from fontParts.base.base import BaseDict, dynamicProperty, reference
from fontParts.base import normalizers
from fontParts.base.deprecated import DeprecatedGroups, RemovedGroups


class BaseGroups(BaseDict, DeprecatedGroups, RemovedGroups):

    """
    A Groups object. This object normally created as part of a
    :class:`BaseFont`. An orphan Groups object can be created like this::

        >>> groups = RGroups()

    This object behaves like a Python dictionary. Most of the dictionary
    functionality comes from :class:`BaseDict`, look at that object for the
    required environment implementation details.

    Groups uses :func:`normalizers.normalizeGroupKey` to normalize the key of
    the ``dict``, and :func:`normalizers.normalizeGroupValue` to normalize the
    value of the ``dict``.
    """

    keyNormalizer = normalizers.normalizeGroupKey
    valueNormalizer = normalizers.normalizeGroupValue

    def _reprContents(self):
        contents = []
        if self.font is not None:
            contents.append("for font")
            contents += self.font._reprContents()
        return contents

    # -------
    # Parents
    # -------

    # Font

    _font = None

    font = dynamicProperty("font", "The Groups' parent :class:`BaseFont`.")

    def _get_font(self):
        if self._font is None:
            return None
        return self._font()

    def _set_font(self, font):
        if self._font is not None and self._font != font:
            raise AssertionError("font for groups already set and is not same as font")
        if font is not None:
            font = reference(font)
        self._font = font

    # ---------
    # Searching
    # ---------

    def findGlyph(self, glyphName):
        """
        Returns a ``list`` of the group or groups associated with
        **glyphName**.
        **glyphName** will be an :ref:`type-string`. If no group is found
        to contain **glyphName** an empty ``list`` will be returned. ::

            >>> font.groups.findGlyph("A")
            ["A_accented"]
        """
        glyphName = normalizers.normalizeGlyphName(glyphName)
        groupNames = self._findGlyph(glyphName)
        groupNames = [self._normalizeKey(
            groupName) for groupName in groupNames]
        return groupNames

    def _findGlyph(self, glyphName):
        """
        This is the environment implementation of
        :meth:`BaseGroups.findGlyph`. **glyphName** will be
        an :ref:`type-string`.

        Subclasses may override this method.
        """
        found = []
        for key, groupList in self.items():
            if glyphName in groupList:
                found.append(key)
        return found

    # --------------
    # Kerning Groups
    # --------------

    side1KerningGroups = dynamicProperty(
        "base_side1KerningGroups",
        """
        All groups marked as potential side 1
        kerning members.

            >>> side1Groups = groups.side1KerningGroups

        The value will be a :ref:`dict` with
        :ref:`string` keys representing group names
        and :ref:`tuple` contaning glyph names.
        """
    )

    def _get_base_side1KerningGroups(self):
        kerningGroups = self._get_side1KerningGroups()
        normalized = {}
        for name, members in kerningGroups.items():
            name = normalizers.normalizeGroupKey(name)
            members = normalizers.normalizeGroupValue(members)
            normalized[name] = members
        return normalized

    def _get_side1KerningGroups(self):
        """
        Subclasses may override this method.
        """
        found = {}
        for name, contents in self.items():
            if name.startswith("public.kern1."):
                found[name] = contents
        return found

    side2KerningGroups = dynamicProperty(
        "base_side2KerningGroups",
        """
        All groups marked as potential side 1
        kerning members.

            >>> side2Groups = groups.side2KerningGroups

        The value will be a :ref:`dict` with
        :ref:`string` keys representing group names
        and :ref:`tuple` contaning glyph names.
        """
    )

    def _get_base_side2KerningGroups(self):
        kerningGroups = self._get_side2KerningGroups()
        normalized = {}
        for name, members in kerningGroups.items():
            name = normalizers.normalizeGroupKey(name)
            members = normalizers.normalizeGroupValue(members)
            normalized[name] = members
        return normalized

    def _get_side2KerningGroups(self):
        """
        Subclasses may override this method.
        """
        found = {}
        for name, contents in self.items():
            if name.startswith("public.kern2."):
                found[name] = contents
        return found

    # ---------------------
    # RoboFab Compatibility
    # ---------------------

    def remove(self, groupName):
        """
        Removes a group from the Groups. **groupName** will be
        a :ref:`type-string` that is the group name to
        be removed.

        This is a backwards compatibility method.
        """
        del self[groupName]

    def asDict(self):
        """
        Return the Groups as a ``dict``.

        This is a backwards compatibility method.
        """
        d = {}
        for k, v in self.items():
            d[k] = v
        return d

    # -------------------
    # Inherited Functions
    # -------------------

    def __contains__(self, groupName):
        """
        Tests to see if a group name is in the Groups.
        **groupName** will be a :ref:`type-string`.
        This returns a ``bool`` indicating if the **groupName**
        is in the Groups. ::

            >>> "myGroup" in font.groups
            True
        """
        return super(BaseGroups, self).__contains__(groupName)

    def __delitem__(self, groupName):
        """
        Removes **groupName** from the Groups. **groupName** is a
        :ref:`type-string`.::

            >>> del font.groups["myGroup"]
        """
        super(BaseGroups, self).__delitem__(groupName)

    def __getitem__(self, groupName):
        """
        Returns the contents of the named group. **groupName** is a
        :ref:`type-string`. The returned value will be a
        :ref:`type-immutable-list` of the group contents.::

            >>> font.groups["myGroup"]
            ("A", "B", "C")

        It is important to understand that any changes to the returned group
        contents will not be reflected in the Groups object. If one wants to
        make a change to the group contents, one should do the following::

            >>> group = font.groups["myGroup"]
            >>> group.remove("A")
            >>> font.groups["myGroup"] = group
        """
        return super(BaseGroups, self).__getitem__(groupName)

    def __iter__(self):
        """
        Iterates through the Groups, giving the key for each iteration. The
        order that the Groups will iterate though is not fixed nor is it
        ordered.::

            >>> for groupName in font.groups:
            >>>     print groupName
            "myGroup"
            "myGroup3"
            "myGroup2"
        """
        return super(BaseGroups, self).__iter__()

    def __len__(self):
        """
        Returns the number of groups in Groups as an ``int``.::

            >>> len(font.groups)
            5
        """
        return super(BaseGroups, self).__len__()

    def __setitem__(self, groupName, glyphNames):
        """
        Sets the **groupName** to the list of **glyphNames**. **groupName**
        is the group name as a :ref:`type-string` and **glyphNames** is a
        ``list`` of glyph names as :ref:`type-string`.

            >>> font.groups["myGroup"] = ["A", "B", "C"]
        """
        super(BaseGroups, self).__setitem__(groupName, glyphNames)

    def clear(self):
        """
        Removes all group information from Groups,
        resetting the Groups to an empty dictionary. ::

            >>> font.groups.clear()
        """
        super(BaseGroups, self).clear()

    def get(self, groupName, default=None):
        """
        Returns the contents of the named group.
        **groupName** is a :ref:`type-string`, and the returned values will
        either be :ref:`type-immutable-list` of group contents or ``None``
        if no group was found. ::

            >>> font.groups["myGroup"]
            ("A", "B", "C")

        It is important to understand that any changes to the returned group
        contents will not be reflected in the Groups object. If one wants to
        make a change to the group contents, one should do the following::

            >>> group = font.groups["myGroup"]
            >>> group.remove("A")
            >>> font.groups["myGroup"] = group
        """
        return super(BaseGroups, self).get(groupName, default)

    def items(self):
        """
        Returns a list of ``tuple`` of each group name and group members.
        Group names are :ref:`type-string` and group members are a
        :ref:`type-immutable-list` of :ref:`type-string`. The initial
        list will be unordered.

            >>> font.groups.items()
            [("myGroup", ("A", "B", "C"), ("myGroup2", ("D", "E", "F"))]
        """
        return super(BaseGroups, self).items()

    def keys(self):
        """
        Returns a ``list`` of all the group names in Groups. This list will be
        unordered.::

            >>> font.groups.keys()
            ["myGroup4", "myGroup1", "myGroup5"]
        """
        return super(BaseGroups, self).keys()

    def pop(self, groupName, default=None):
        """
        Removes the **groupName** from the Groups and returns the list of
        group members. If no group is found, **default** is returned.
        **groupName** is a :ref:`type-string`. This must return either
        **default** or a :ref:`type-immutable-list` of glyph names as
        :ref:`type-string`.

            >>> font.groups.pop("myGroup")
            ("A", "B", "C")
        """
        return super(BaseGroups, self).pop(groupName, default)

    def update(self, otherGroups):
        """
        Updates the Groups based on **otherGroups**. *otherGroups** is a
        ``dict`` of groups information. If a group from **otherGroups** is in
        Groups, the group members will be replaced by the group members from
        **otherGroups**. If a group from **otherGroups** is not in the Groups,
        it is added to the Groups. If Groups contain a group name that is not
        in *otherGroups**, it is not changed.

            >>> font.groups.update(newGroups)
        """
        super(BaseGroups, self).update(otherGroups)

    def values(self):
        """
        Returns a ``list`` of each named group's members.
        This will be a list of lists, the group members will be a
        :ref:`type-immutable-list` of :ref:`type-string`. The initial
        list will be unordered.

            >>> font.groups.items()
            [("A", "B", "C"), ("D", "E", "F")]
        """
        return super(BaseGroups, self).values()
