import os
from fontTools import ufoLib
from fontParts.base.errors import FontPartsError
from fontParts.base.base import dynamicProperty, InterpolationMixin
from fontParts.base.layer import _BaseGlyphVendor
from fontParts.base import normalizers
from fontParts.base.compatibility import FontCompatibilityReporter
from fontParts.base.deprecated import DeprecatedFont, RemovedFont


class BaseFont(
               _BaseGlyphVendor,
               InterpolationMixin,
               DeprecatedFont,
               RemovedFont
               ):

    """
    A font object. This object is almost always
    created with one of the font functions in
    :ref:`fontparts-world`.
    """

    def __init__(self, pathOrObject=None, showInterface=True):
        """
        When constructing a font, the object can be created
        in a new file, from an existing file or from a native
        object. This is defined with the **pathOrObjectArgument**.
        If **pathOrObject** is a string, the string must represent
        an existing file. If **pathOrObject** is an instance of the
        environment's unwrapped native font object, wrap it with
        FontParts. If **pathOrObject** is None, create a new,
        empty font. If **showInterface** is ``False``, the font
        should be created without graphical interface. The default
        for **showInterface** is ``True``.
        """
        super(BaseFont, self).__init__(pathOrObject=pathOrObject,
                                       showInterface=showInterface)

    def _reprContents(self):
        contents = [
            "'%s %s'" % (self.info.familyName, self.info.styleName),
        ]
        if self.path is not None:
            contents.append("path=%r" % self.path)
        return contents

    # ----
    # Copy
    # ----

    copyAttributes = (
        "info",
        "groups",
        "kerning",
        "features",
        "lib",
        "layerOrder",
        "defaultLayerName",
        "glyphOrder"
    )

    def copy(self):
        """
        Copy the font into a new font. ::

            >>> copiedFont = font.copy()

        This will copy:

        * info
        * groups
        * kerning
        * features
        * lib
        * layers
        * layerOrder
        * defaultLayerName
        * glyphOrder
        * guidelines
        """
        return super(BaseFont, self).copy()

    def copyData(self, source):
        """
        Copy data from **source** into this font.
        Refer to :meth:`BaseFont.copy` for a list
        of values that will be copied.
        """
        # set the default layer name
        self.defaultLayer.name = source.defaultLayerName
        for layerName in source.layerOrder:
            if layerName in self.layerOrder:
                layer = self.getLayer(layerName)
            else:
                layer = self.newLayer(layerName)
            layer.copyData(source.getLayer(layerName))
        for guideline in self.guidelines:
            self.appendGuideline(guideline)
        super(BaseFont, self).copyData(source)

    # ---------------
    # File Operations
    # ---------------

    # Initialize

    def _init(self, pathOrObject=None, showInterface=True, **kwargs):
        """
        Initialize this object. This should wrap a native font
        object based on the values for **pathOrObject**:

        +--------------------+---------------------------------------------------+
        | None               | Create a new font.                                |
        +--------------------+---------------------------------------------------+
        | string             | Open the font file located at the given location. |
        +--------------------+---------------------------------------------------+
        | native font object | Wrap the given object.                            |
        +--------------------+---------------------------------------------------+

        If **showInterface** is ``False``, the font should be
        created without graphical interface.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # path

    path = dynamicProperty(
        "base_path",
        """
        The path to the file this object represents. ::

            >>> print font.path
            "/path/to/my/font.ufo"
        """
    )

    def _get_base_path(self):
        path = self._get_path()
        if path is not None:
            path = normalizers.normalizeFilePath(path)
        return path

    def _get_path(self, **kwargs):
        """
        This is the environment implementation of
        :attr:`BaseFont.path`.

        This must return a :ref:`type-string` defining the
        location of the file or ``None`` indicating that the
        font does not have a file representation. If the
        returned value is not ``None`` it will be normalized
        with :func:`normalizers.normalizeFilePath`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # save

    def save(self, path=None, showProgress=False, formatVersion=None, fileStructure=None):
        """
        Save the font to **path**.

            >>> font.save()
            >>> font.save("/path/to/my/font-2.ufo")

        If **path** is None, use the font's original location.
        The file type must be inferred from the file extension
        of the given path. If no file extension is given, the
        environment may fall back to the format of its choice.
        **showProgress** indicates if a progress indicator should
        be displayed during the operation. Environments may or may
        not implement this behavior. **formatVersion** indicates
        the format version that should be used for writing the given
        file type. For example, if 2 is given for formatVersion
        and the file type being written if UFO, the file is to
        be written in UFO 2 format. This value is not limited
        to UFO format versions. If no format version is given,
        the original format version of the file should be preserved.
        If there is no original format version it is implied that
        the format version is the latest version for the file
        type as supported by the environment. **fileStructure** indicates
        the file structure of the written ufo. The **fileStructure** can
        either be None, 'zip' or 'package', None will use the existing file
        strucure or the default one for unsaved font. 'package' is the default
        file structure and 'zip' will save the font to .ufoz.

        .. note::

           Environments may define their own rules governing when
           a file should be saved into its original location and
           when it should not. For example, a font opened from a
           compiled OpenType font may not be written back into
           the original OpenType font.
        """
        if path is None and self.path is None:
            raise IOError(("The font cannot be saved because no file "
                           "location has been given."))
        if path is not None:
            path = normalizers.normalizeFilePath(path)
        showProgress = bool(showProgress)
        if formatVersion is not None:
            formatVersion = normalizers.normalizeFileFormatVersion(
                formatVersion)
        if fileStructure is not None:
            fileStructure = normalizers.normalizeFileStructure(fileStructure)
        self._save(path=path, showProgress=showProgress,
                   formatVersion=formatVersion, fileStructure=fileStructure)

    def _save(self, path=None, showProgress=False,
              formatVersion=None, fileStructure=None, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.save`. **path** will be a
        :ref:`type-string` or ``None``. If **path**
        is not ``None``, the value will have been
        normalized with :func:`normalizers.normalizeFilePath`.
        **showProgress** will be a ``bool`` indicating if
        the environment should display a progress bar
        during the operation. Environments are not *required*
        to display a progress bar even if **showProgess**
        is ``True``. **formatVersion** will be :ref:`type-int-float`
        or ``None`` indicating the file format version
        to write the data into. It will have been normalized
        with :func:`normalizers.normalizeFileFormatVersion`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # close

    def close(self, save=False):
        """
        Close the font.

            >>> font.close()

        **save** is a boolean indicating if the font
        should be saved prior to closing. If **save**
        is ``True``, the :meth:`BaseFont.save` method
        will be called. The default is ``False``.
        """
        if save:
            self.save()
        self._close()

    def _close(self, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.close`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # generate

    @staticmethod
    def generateFormatToExtension(format, fallbackFormat):
        """
        +--------------+--------------------------------------------------------------------+
        | mactype1     | Mac Type 1 font (generates suitcase  and LWFN file)                |
        +--------------+--------------------------------------------------------------------+
        | macttf       | Mac TrueType font (generates suitcase)                             |
        +--------------+--------------------------------------------------------------------+
        | macttdfont   | Mac TrueType font (generates suitcase with resources in data fork) |
        +--------------+--------------------------------------------------------------------+
        | otfcff       | PS OpenType (CFF-based) font (OTF)                                 |
        +--------------+--------------------------------------------------------------------+
        | otfttf       | PC TrueType/TT OpenType font (TTF)                                 |
        +--------------+--------------------------------------------------------------------+
        | pctype1      | PC Type 1 font (binary/PFB)                                        |
        +--------------+--------------------------------------------------------------------+
        | pcmm         | PC MultipleMaster font (PFB)                                       |
        +--------------+--------------------------------------------------------------------+
        | pctype1ascii | PC Type 1 font (ASCII/PFA)                                         |
        +--------------+--------------------------------------------------------------------+
        | pcmmascii    | PC MultipleMaster font (ASCII/PFA)                                 |
        +--------------+--------------------------------------------------------------------+
        | ufo1         | UFO format version 1                                               |
        +--------------+--------------------------------------------------------------------+
        | ufo2         | UFO format version 2                                               |
        +--------------+--------------------------------------------------------------------+
        | ufo3         | UFO format version 3                                               |
        +--------------+--------------------------------------------------------------------+
        | unixascii    | UNIX ASCII font (ASCII/PFA)                                        |
        +--------------+--------------------------------------------------------------------+
        """
        formatToExtension = dict(
            # mactype1=None,
            macttf=".ttf",
            macttdfont=".dfont",
            otfcff=".otf",
            otfttf=".ttf",
            # pctype1=None,
            # pcmm=None,
            # pctype1ascii=None,
            # pcmmascii=None,
            ufo1=".ufo",
            ufo2=".ufo",
            ufo3=".ufo",
            unixascii=".pfa",
        )
        return formatToExtension.get(format, fallbackFormat)

    def generate(self, format, path=None, **environmentOptions):
        """
        Generate the font to another format.

            >>> font.generate("otfcff")
            >>> font.generate("otfcff", "/path/to/my/font.otf")

        **format** defines the file format to output.
        Standard format identifiers can be found in :attr:`BaseFont.generateFormatToExtension`:


        Environments are not required to support all of these
        and environments may define their own format types.
        **path** defines the location where the new file should
        be created. If a file already exists at that location,
        it will be overwritten by the new file. If **path** defines
        a directory, the file will be output as the current
        file name, with the appropriate suffix for the format,
        into the given directory. If no **path** is given, the
        file will be output into the same directory as the source
        font with the file named with the current file name,
        with the appropriate suffix for the format.

        Environments may allow unique keyword arguments in this
        method. For example, if a tool allows decomposing components
        during a generate routine it may allow this:

            >>> font.generate("otfcff", "/p/f.otf", decompose=True)
        """
        import warnings
        if format is None:
            raise ValueError("The format must be defined when generating.")
        elif not isinstance(format, str):
            raise TypeError("The format must be defined as a string.")
        env = {}
        for key, value in environmentOptions.items():
            valid = self._isValidGenerateEnvironmentOption(key)
            if not valid:
                warnings.warn("The %s argument is not supported "
                              "in this environment." % key, UserWarning)
            env[key] = value
        environmentOptions = env
        ext = self.generateFormatToExtension(format, "." + format)
        if path is None and self.path is None:
            raise IOError(("The file cannot be generated because an "
                           "output path was not defined."))
        elif path is None:
            path = os.path.splitext(self.path)[0]
            path += ext
        elif os.path.isdir(path):
            if self.path is None:
                raise IOError(("The file cannot be generated because "
                               "the file does not have a path."))
            fileName = os.path.basename(self.path)
            fileName += ext
            path = os.path.join(path, fileName)
        path = normalizers.normalizeFilePath(path)
        return self._generate(
            format=format,
            path=path,
            environmentOptions=environmentOptions
        )

    @staticmethod
    def _isValidGenerateEnvironmentOption(name):
        """
        Any unknown keyword arguments given to :meth:`BaseFont.generate`
        will be passed to this method. **name** will be the name
        used for the argument. Environments may evaluate if **name**
        is a supported option. If it is, they must return `True` if
        it is not, they must return `False`.

        Subclasses may override this method.
        """
        return False

    def _generate(self, format, path, environmentOptions, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.generate`. **format** will be a
        :ref:`type-string` defining the output format.
        Refer to the :meth:`BaseFont.generate` documentation
        for the standard format identifiers. If the value
        given for **format** is not supported by the environment,
        the environment must raise :exc:`FontPartsError`.
        **path** will be a :ref:`type-string` defining the
        location where the file should be created. It
        will have been normalized with :func:`normalizers.normalizeFilePath`.
        **environmentOptions** will be a dictionary of names
        validated with :meth:`BaseFont._isValidGenerateEnvironmentOption`
        nd the given values. These values will not have been passed
        through any normalization functions.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # -----------
    # Sub-Objects
    # -----------

    # info

    info = dynamicProperty(
        "base_info",
        """
        The font's :class:`BaseInfo` object.

            >>> font.info.familyName
            "My Family"
        """
    )

    def _get_base_info(self):
        info = self._get_info()
        info.font = self
        return info

    def _get_info(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.info`. This must return an
        instance of a :class:`BaseInfo` subclass.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # groups

    groups = dynamicProperty(
        "base_groups",
        """
        The font's :class:`BaseGroups` object.

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

    def _get_base_groups(self):
        groups = self._get_groups()
        groups.font = self
        return groups

    def _get_groups(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.groups`. This must return
        an instance of a :class:`BaseGroups` subclass.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # kerning

    kerning = dynamicProperty(
        "base_kerning",
        """
        The font's :class:`BaseKerning` object.

            >>> font.kerning["A", "B"]
            -100
        """
    )

    def _get_base_kerning(self):
        kerning = self._get_kerning()
        kerning.font = self
        return kerning

    def _get_kerning(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.kerning`. This must return
        an instance of a :class:`BaseKerning` subclass.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def getFlatKerning(self):
        """
        Get the font's kerning as a flat dictionary.
        """
        return self._getFlatKerning()

    def _getFlatKerning(self):
        """
        This is the environment implementation of
        :meth:`BaseFont.getFlatKerning`.

        Subclasses may override this method.
        """
        kernOrder = {
            (True, True): 0,  # group group
            (True, False): 1,  # group glyph
            (False, True): 2,  # glyph group
            (False, False): 3,  # glyph glyph
        }

        def kerningSortKeyFunc(pair):
            g1, g2 = pair
            g1grp = g1.startswith("public.kern1.")
            g2grp = g2.startswith("public.kern2.")
            return (kernOrder[g1grp, g2grp], pair)

        flatKerning = dict()
        kerning = self.kerning
        groups = self.groups

        for pair in sorted(self.kerning.keys(), key=kerningSortKeyFunc):
            kern = kerning[pair]
            (left, right) = pair
            if left.startswith("public.kern1."):
                left = groups.get(left, [])
            else:
                left = [left]

            if right.startswith("public.kern2."):
                right = groups.get(right, [])
            else:
                right = [right]

            for r in right:
                for l in left:
                    flatKerning[(l, r)] = kern

        return flatKerning

    # features

    features = dynamicProperty(
        "base_features",
        """
        The font's :class:`BaseFeatures` object.

            >>> font.features.text
            "include(features/substitutions.fea);"
        """
    )

    def _get_base_features(self):
        features = self._get_features()
        features.font = self
        return features

    def _get_features(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.features`. This must return
        an instance of a :class:`BaseFeatures` subclass.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # lib

    lib = dynamicProperty(
        "base_lib",
        """
        The font's :class:`BaseLib` object.

            >>> font.lib["org.robofab.hello"]
            "world"
        """
    )

    def _get_base_lib(self):
        lib = self._get_lib()
        lib.font = self
        return lib

    def _get_lib(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.lib`. This must return an
        instance of a :class:`BaseLib` subclass.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # tempLib

    tempLib = dynamicProperty(
        "base_tempLib",
        """
        The font's :class:`BaseLib` object. ::

            >>> font.tempLib["org.robofab.hello"]
            "world"
        """
    )

    def _get_base_tempLib(self):
        lib = self._get_tempLib()
        lib.font = self
        return lib

    def _get_tempLib(self):
        """
        This is the environment implementation of :attr:`BaseLayer.tempLib`.
        This must return an instance of a :class:`BaseLib` subclass.
        """
        self.raiseNotImplementedError()

    # -----------------
    # Layer Interaction
    # -----------------

    layers = dynamicProperty(
        "base_layers",
        """
        The font's :class:`BaseLayer` objects.

            >>> for layer in font.layers:
            ...     layer.name
            "My Layer 1"
            "My Layer 2"
        """
    )

    def _get_base_layers(self):
        layers = self._get_layers()
        for layer in layers:
            self._setFontInLayer(layer)
        return tuple(layers)

    def _get_layers(self, **kwargs):
        """
        This is the environment implementation of
        :attr:`BaseFont.layers`. This must return an
        :ref:`type-immutable-list` containing
        instances of :class:`BaseLayer` subclasses.
        The items in the list should be in the order
        defined by :attr:`BaseFont.layerOrder`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # order

    layerOrder = dynamicProperty(
        "base_layerOrder",
        """
        A list of layer names indicating order of the layers in the font.

            >>> font.layerOrder = ["My Layer 2", "My Layer 1"]
            >>> font.layerOrder
            ["My Layer 2", "My Layer 1"]
        """
    )

    def _get_base_layerOrder(self):
        value = self._get_layerOrder()
        value = normalizers.normalizeLayerOrder(value, self)
        return list(value)

    def _set_base_layerOrder(self, value):
        value = normalizers.normalizeLayerOrder(value, self)
        self._set_layerOrder(value)

    def _get_layerOrder(self, **kwargs):
        """
        This is the environment implementation of
        :attr:`BaseFont.layerOrder`. This must return an
        :ref:`type-immutable-list` defining the order of
        the layers in the font. The contents of the list
        must be layer names as :ref:`type-string`. The
        list will be normalized with :func:`normalizers.normalizeLayerOrder`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _set_layerOrder(self, value, **kwargs):
        """
        This is the environment implementation of
        :attr:`BaseFont.layerOrder`. **value** will
        be a **list** of :ref:`type-string` representing
        layer names. The list will have been normalized
        with :func:`normalizers.normalizeLayerOrder`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # default layer

    def _setFontInLayer(self, layer):
        if layer.font is None:
            layer.font = self

    defaultLayerName = dynamicProperty(
        "base_defaultLayerName",
        """
        The name of the font's default layer.

            >>> font.defaultLayerName = "My Layer 2"
            >>> font.defaultLayerName
            "My Layer 2"
        """
    )

    def _get_base_defaultLayerName(self):
        value = self._get_defaultLayerName()
        value = normalizers.normalizeDefaultLayerName(value, self)
        return value

    def _set_base_defaultLayerName(self, value):
        value = normalizers.normalizeDefaultLayerName(value, self)
        self._set_defaultLayerName(value)

    def _get_defaultLayerName(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.defaultLayerName`. Return the
        name of the default layer as a :ref:`type-string`.
        The name will be normalized with
        :func:`normalizers.normalizeDefaultLayerName`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _set_defaultLayerName(self, value, **kwargs):
        """
        This is the environment implementation of
        :attr:`BaseFont.defaultLayerName`. **value**
        will be a :ref:`type-string`. It will have
        been normalized with :func:`normalizers.normalizeDefaultLayerName`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    defaultLayer = dynamicProperty(
        "base_defaultLayer",
        """
        The font's default layer.

            >>> layer = font.defaultLayer
            >>> font.defaultLayer = otherLayer
        """
    )

    def _get_defaultLayer(self):
        layer = self._get_base_defaultLayer()
        layer = normalizers.normalizeLayer(layer)
        return layer

    def _set_defaultLayer(self, layer):
        layer = normalizers.normalizeLayer(layer)
        self._set_base_defaultLayer(layer)

    def _get_base_defaultLayer(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.defaultLayer`. Return the
        default layer as a :class:`BaseLayer` object.
        The layer will be normalized with
        :func:`normalizers.normalizeLayer`.

        Subclasses must override this method.
        """
        name = self.defaultLayerName
        layer = self.getLayer(name)
        return layer

    def _set_base_defaultLayer(self, value):
        """
        This is the environment implementation of
        :attr:`BaseFont.defaultLayer`. **value**
        will be a :class:`BaseLayer`. It will have
        been normalized with :func:`normalizers.normalizeLayer`.

        Subclasses must override this method.
        """
        self.defaultLayerName = value.name

    # get

    def getLayer(self, name):
        """
        Get the :class:`BaseLayer` with **name**.

            >>> layer = font.getLayer("My Layer 2")
        """
        name = normalizers.normalizeLayerName(name)
        if name not in self.layerOrder:
            raise ValueError("No layer with the name '%s' exists." % name)
        layer = self._getLayer(name)
        self._setFontInLayer(layer)
        return layer

    def _getLayer(self, name, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.getLayer`. **name** will
        be a :ref:`type-string`. It will have been
        normalized with :func:`normalizers.normalizeLayerName`
        and it will have been verified as an existing layer.
        This must return an instance of :class:`BaseLayer`.

        Subclasses may override this method.
        """
        for layer in self.layers:
            if layer.name == name:
                return layer

    # new

    def newLayer(self, name, color=None):
        """
        Make a new layer with **name** and **color**.
        **name** must be a :ref:`type-string` and
        **color** must be a :ref:`type-color` or ``None``.

            >>> layer = font.newLayer("My Layer 3")

        The will return the newly created
        :class:`BaseLayer`.
        """
        name = normalizers.normalizeLayerName(name)
        if name in self.layerOrder:
            layer = self.getLayer(name)
            if color is not None:
                layer.color = color
            return layer
        if color is not None:
            color = normalizers.normalizeColor(color)
        layer = self._newLayer(name=name, color=color)
        self._setFontInLayer(layer)
        return layer

    def _newLayer(self, name, color, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.newLayer`. **name** will be
        a :ref:`type-string` representing a valid
        layer name. The value will have been normalized
        with :func:`normalizers.normalizeLayerName` and
        **name** will not be the same as the name of
        an existing layer. **color** will be a
        :ref:`type-color` or ``None``. If the value
        is not ``None`` the value will have been
        normalized with :func:`normalizers.normalizeColor`.
        This must return an instance of a :class:`BaseLayer`
        subclass that represents the new layer.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # remove

    def removeLayer(self, name):
        """
        Remove the layer with **name** from the font.

            >>> font.removeLayer("My Layer 3")
        """
        name = normalizers.normalizeLayerName(name)
        if name not in self.layerOrder:
            raise ValueError("No layer with the name '%s' exists." % name)
        self._removeLayer(name)

    def _removeLayer(self, name, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.removeLayer`. **name** will
        be a :ref:`type-string` defining the name
        of an existing layer. The value will have
        been normalized with :func:`normalizers.normalizeLayerName`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # insert

    def insertLayer(self, layer, name=None):
        """
        Insert **layer** into the font. ::

            >>> layer = font.insertLayer(otherLayer, name="layer 2")

        This will not insert the layer directly.
        Rather, a new layer will be created and the data from
        **layer** will be copied to to the new layer. **name**
        indicates the name that should be assigned to the layer
        after insertion. If **name** is not given, the layer's
        original name must be used. If the layer does not have
        a name, an error must be raised. The data that will be
        inserted from **layer** is the same data as documented
        in :meth:`BaseLayer.copy`.
        """
        if name is None:
            name = layer.name
        name = normalizers.normalizeLayerName(name)
        if name in self:
            self.removeLayer(name)
        return self._insertLayer(layer, name=name)

    def _insertLayer(self, layer, name, **kwargs):
        """
        This is the environment implementation of :meth:`BaseFont.insertLayer`.
        This must return an instance of a :class:`BaseLayer` subclass.
        **layer** will be a layer object with the attributes necessary
        for copying as defined in :meth:`BaseLayer.copy` An environment
        must not insert **layer** directly. Instead the data from **layer**
        should be copied to a new layer. **name** will be a :ref:`type-string`
        representing a glyph layer. It will have been normalized with
        :func:`normalizers.normalizeLayerName`. **name** will have been
        tested to make sure that no layer with the same name exists in the font.

        Subclasses may override this method.
        """
        if name != layer.name and layer.name in self.layerOrder:
            layer = layer.copy()
            layer.name = name
        dest = self.newLayer(name)
        dest.copyData(layer)
        return dest

    # duplicate

    def duplicateLayer(self, layerName, newLayerName):
        """
        Duplicate the layer with **layerName**, assign
        **newLayerName** to the new layer and insert the
        new layer into the font. ::

            >>> layer = font.duplicateLayer("layer 1", "layer 2")
        """
        layerOrder = self.layerOrder
        layerName = normalizers.normalizeLayerName(layerName)
        if layerName not in layerOrder:
            raise ValueError("No layer with the name '%s' exists." % layerName)
        newLayerName = normalizers.normalizeLayerName(newLayerName)
        if newLayerName in layerOrder:
            raise ValueError("A layer with the name '%s' already exists." % newLayerName)
        newLayer = self._duplicateLayer(layerName, newLayerName)
        newLayer = normalizers.normalizeLayer(newLayer)
        return newLayer

    def _duplicateLayer(self, layerName, newLayerName):
        """
        This is the environment implementation of :meth:`BaseFont.duplicateLayer`.
        **layerName** will be a :ref:`type-string` representing a valid layer name.
        The value will have been normalized with :func:`normalizers.normalizeLayerName`
        and **layerName** will be a layer that exists in the font. **newLayerName**
        will be a :ref:`type-string` representing a valid layer name. The value will
        have been normalized with :func:`normalizers.normalizeLayerName` and
        **newLayerName** will have been tested to make sure that no layer with
        the same name exists in the font. This must return an instance of a
        :class:`BaseLayer` subclass.

        Subclasses may override this method.
        """
        newLayer = self.getLayer(layerName).copy()
        return self.insertLayer(newLayer, newLayerName)

    def swapLayerNames(self, layerName, otherLayerName):
        """
        Assign **layerName** to the layer currently named
        **otherLayerName** and assign the name **otherLayerName**
        to the layer currently named **layerName**.

            >>> font.swapLayerNames("before drawing revisions", "after drawing revisions")
        """
        layerOrder = self.layerOrder
        layerName = normalizers.normalizeLayerName(layerName)
        if layerName not in layerOrder:
            raise ValueError("No layer with the name '%s' exists." % layerName)
        otherLayerName = normalizers.normalizeLayerName(otherLayerName)
        if otherLayerName not in layerOrder:
            raise ValueError("No layer with the name '%s' exists." % otherLayerName)
        self._swapLayers(layerName, otherLayerName)

    def _swapLayers(self, layerName, otherLayerName):
        """
        This is the environment implementation of :meth:`BaseFont.swapLayerNames`.
        **layerName** will be a :ref:`type-string` representing a valid layer name.
        The value will have been normalized with :func:`normalizers.normalizeLayerName`
        and **layerName** will be a layer that exists in the font. **otherLayerName**
        will be a :ref:`type-string` representing a valid layer name. The value will
        have been normalized with :func:`normalizers.normalizeLayerName` and
        **otherLayerName** will be a layer that exists in the font.

        Subclasses may override this method.
        """
        import random
        layer1 = self.getLayer(layerName)
        layer2 = self.getLayer(otherLayerName)
        # make a temporary name and assign it to
        # the first layer to prevent two layers
        # from having the same name at once.
        layerOrder = self.layerOrder
        for _ in range(50):
            # shout out to PostScript unique IDs
            tempLayerName = str(random.randint(4000000, 4999999))
            if tempLayerName not in layerOrder:
                break
        if tempLayerName in layerOrder:
            raise FontPartsError(("Couldn't find a temporary layer name "
                                  "after 50 tries. Sorry. Please try again."))
        layer1.name = tempLayerName
        # now swap
        layer2.name = layerName
        layer1.name = otherLayerName

    # -----------------
    # Glyph Interaction
    # -----------------

    # base implementation overrides

    def _getItem(self, name, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.__getitem__`. **name** will
        be a :ref:`type-string` defining an existing
        glyph in the default layer. The value will
        have been normalized with :func:`normalizers.normalizeGlyphName`.

        Subclasses may override this method.
        """
        layer = self.defaultLayer
        return layer[name]

    def _keys(self, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.keys`. This must return an
        :ref:`type-immutable-list` of all glyph names
        in the default layer.

        Subclasses may override this method.
        """
        layer = self.defaultLayer
        return layer.keys()

    def _newGlyph(self, name, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.newGlyph`. **name** will be
        a :ref:`type-string` representing a valid
        glyph name. The value will have been tested
        to make sure that an existing glyph in the
        default layer does not have an identical name.
        The value will have been normalized with
        :func:`normalizers.normalizeGlyphName`. This
        must return an instance of :class:`BaseGlyph`
        representing the new glyph.

        Subclasses may override this method.
        """
        layer = self.defaultLayer
        # clear is False here because the base newFont
        # that has called this method will have already
        # handled the clearing as specified by the caller.
        return layer.newGlyph(name, clear=False)

    def _removeGlyph(self, name, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.removeGlyph`. **name** will
        be a :ref:`type-string` representing an
        existing glyph in the default layer. The
        value will have been normalized with
        :func:`normalizers.normalizeGlyphName`.

        Subclasses may override this method.
        """
        layer = self.defaultLayer
        layer.removeGlyph(name)

    def __setitem__(self, name, glyph):
        """
        Insert **glyph** into the font. ::

            >>> glyph = font["A"] = otherGlyph

        This will not insert the glyph directly. Rather, a
        new glyph will be created and the data from **glyph**
        will be copied to the new glyph. **name** indicates
        the name that should be assigned to the glyph after
        insertion. The data that will be inserted
        from **glyph** is the same data as documented in
        :meth:`BaseGlyph.copy`.

        On a font level **font.glyphOrder** will be preserved
        if the **name** is already present.
        """
        name = normalizers.normalizeGlyphName(name)
        if name in self:
            # clear the glyph here if the glyph exists
            dest = self._getItem(name)
            dest.clear()
        return self._insertGlyph(glyph, name=name, clear=False)

    # order

    glyphOrder = dynamicProperty(
        "base_glyphOrder",
        """
        The preferred order of the glyphs in the font.

            >>> font.glyphOrder
            ["C", "B", "A"]
            >>> font.glyphOrder = ["A", "B", "C"]
        """
    )

    def _get_base_glyphOrder(self):
        value = self._get_glyphOrder()
        value = normalizers.normalizeGlyphOrder(value)
        return value

    def _set_base_glyphOrder(self, value):
        value = normalizers.normalizeGlyphOrder(value)
        self._set_glyphOrder(value)

    def _get_glyphOrder(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.glyphOrder`. This must return
        an :ref:`type-immutable-list` containing glyph
        names representing the glyph order in the font.
        The value will be normalized with
        :func:`normalizers.normalizeGlyphOrder`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _set_glyphOrder(self, value):
        """
        This is the environment implementation of
        :attr:`BaseFont.glyphOrder`. **value** will
        be a list of :ref:`type-string`. It will
        have been normalized with
        :func:`normalizers.normalizeGlyphOrder`.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    # -----------------
    # Global Operations
    # -----------------

    def round(self):
        """
        Round all approriate data to integers.

            >>> font.round()

        This is the equivalent of calling the round method on:

        * info
        * kerning
        * the default layer
        * font-level guidelines

        This applies only to the default layer.
        """
        self._round()

    def _round(self):
        """
        This is the environment implementation of
        :meth:`BaseFont.round`.

        Subclasses may override this method.
        """
        layer = self.defaultLayer
        layer.round()
        self.info.round()
        self.kerning.round()
        for guideline in self.guidelines:
            guideline.round()

    def autoUnicodes(self):
        """
        Use heuristics to set Unicode values in all glyphs.

            >>> font.autoUnicodes()

        Environments will define their own heuristics for
        automatically determining values.

        This applies only to the default layer.
        """
        self._autoUnicodes()

    def _autoUnicodes(self):
        """
        This is the environment implementation of
        :meth:`BaseFont.autoUnicodes`.

        Subclasses may override this method.
        """
        layer = self.defaultLayer
        layer.autoUnicodes()

    # ----------
    # Guidelines
    # ----------

    def _setFontInGuideline(self, guideline):
        if guideline.font is None:
            guideline.font = self

    guidelines = dynamicProperty(
        "guidelines",
        """
        An :ref:`type-immutable-list` of font-level :class:`BaseGuideline` objects.

            >>> for guideline in font.guidelines:
            ...     guideline.angle
            0
            45
            90
        """
    )

    def _get_guidelines(self):
        """
        This is the environment implementation of
        :attr:`BaseFont.guidelines`. This must
        return an :ref:`type-immutable-list` of
        :class:`BaseGuideline` objects.

        Subclasses may override this method.
        """
        return tuple([self._getitem__guidelines(i)
                      for i in range(self._len__guidelines())])

    def _len__guidelines(self):
        return self._lenGuidelines()

    def _lenGuidelines(self, **kwargs):
        """
        This must return an integer indicating
        the number of font-level guidelines
        in the font.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _getitem__guidelines(self, index):
        index = normalizers.normalizeIndex(index)
        if index >= self._len__guidelines():
            raise ValueError("No guideline located at index %d." % index)
        guideline = self._getGuideline(index)
        self._setFontInGuideline(guideline)
        return guideline

    def _getGuideline(self, index, **kwargs):
        """
        This must return a :class:`BaseGuideline` object.
        **index** will be a valid **index**.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def _getGuidelineIndex(self, guideline):
        for i, other in enumerate(self.guidelines):
            if guideline == other:
                return i
        raise FontPartsError("The guideline could not be found.")

    def appendGuideline(self, position=None, angle=None, name=None, color=None, guideline=None):
        """
        Append a new guideline to the font.

            >>> guideline = font.appendGuideline((50, 0), 90)
            >>> guideline = font.appendGuideline((0, 540), 0, name="overshoot",
            >>> color=(0, 0, 0, 0.2))

        **position** must be a :ref:`type-coordinate`
        indicating the position of the guideline.
        **angle** indicates the :ref:`type-angle` of
        the guideline. **name** indicates the name
        for the guideline. This must be a :ref:`type-string`
        or ``None``. **color** indicates the color for
        the guideline. This must be a :ref:`type-color`
        or ``None``. This will return the newly created
        :class:`BaseGuidline` object.

        ``guideline`` may be a :class:`BaseGuideline` object from which
        attribute values will be copied. If ``position``, ``angle``, ``name``
        or ``color`` are specified as arguments, those values will be used
        instead of the values in the given guideline object.
        """
        identifier = None
        if guideline is not None:
            guideline = normalizers.normalizeGuideline(guideline)
            if position is None:
                position = guideline.position
            if angle is None:
                angle = guideline.angle
            if name is None:
                name = guideline.name
            if color is None:
                color = guideline.color
            if guideline.identifier is not None:
                existing = set([g.identifier for g in self.guidelines if g.identifier is not None])
                if guideline.identifier not in existing:
                    identifier = guideline.identifier
        position = normalizers.normalizeCoordinateTuple(position)
        angle = normalizers.normalizeRotationAngle(angle)
        if name is not None:
            name = normalizers.normalizeGuidelineName(name)
        if color is not None:
            color = normalizers.normalizeColor(color)
        identifier = normalizers.normalizeIdentifier(identifier)
        guideline = self._appendGuideline(position, angle, name=name, color=color, identifier=identifier)
        guideline.font = self
        return guideline

    def _appendGuideline(self, position, angle, name=None, color=None, identifier=None, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.appendGuideline`. **position**
        will be a valid :ref:`type-coordinate`. **angle**
        will be a valid angle. **name** will be a valid
        :ref:`type-string` or ``None``. **color** will
        be a valid :ref:`type-color` or ``None``.
        This must return the newly created
        :class:`BaseGuideline` object.

        Subclasses may override this method.
        """
        self.raiseNotImplementedError()

    def removeGuideline(self, guideline):
        """
        Remove **guideline** from the font.

            >>> font.removeGuideline(guideline)
            >>> font.removeGuideline(2)

        **guideline** can be a guideline object or
        an integer representing the guideline index.
        """
        if isinstance(guideline, int):
            index = guideline
        else:
            index = self._getGuidelineIndex(guideline)
        index = normalizers.normalizeIndex(index)
        if index >= self._len__guidelines():
            raise ValueError("No guideline located at index %d." % index)
        self._removeGuideline(index)

    def _removeGuideline(self, index, **kwargs):
        """
        This is the environment implementation of
        :meth:`BaseFont.removeGuideline`. **index**
        will be a valid index.

        Subclasses must override this method.
        """
        self.raiseNotImplementedError()

    def clearGuidelines(self):
        """
        Clear all guidelines.

            >>> font.clearGuidelines()
        """
        self._clearGuidelines()

    def _clearGuidelines(self):
        """
        This is the environment implementation of
        :meth:`BaseFont.clearGuidelines`.

        Subclasses may override this method.
        """
        for _ in range(self._len__guidelines()):
            self.removeGuideline(-1)

    # -------------
    # Interpolation
    # -------------

    def interpolate(self, factor, minFont, maxFont,
                    round=True, suppressError=True):
        """
        Interpolate all possible data in the font.

            >>> font.interpolate(0.5, otherFont1, otherFont2)
            >>> font.interpolate((0.5, 2.0), otherFont1, otherFont2, round=False)

        The interpolation occurs on a 0 to 1.0 range where **minFont**
        is located at 0 and **maxFont** is located at 1.0. **factor**
        is the interpolation value. It may be less than 0 and greater
        than 1.0. It may be a :ref:`type-int-float` or a tuple of
        two :ref:`type-int-float`. If it is a tuple, the first
        number indicates the x factor and the second number indicates
        the y factor. **round** indicates if the result should be
        rounded to integers. **suppressError** indicates if incompatible
        data should be ignored or if an error should be raised when
        such incompatibilities are found.
        """
        factor = normalizers.normalizeInterpolationFactor(factor)
        if not isinstance(minFont, BaseFont):
            raise TypeError(("Interpolation to an instance of %r can not be "
                             "performed from an instance of %r.")
                            % (self.__class__.__name__, minFont.__class__.__name__))
        if not isinstance(maxFont, BaseFont):
            raise TypeError(("Interpolation to an instance of %r can not be "
                             "performed from an instance of %r.")
                            % (self.__class__.__name__, maxFont.__class__.__name__))
        round = normalizers.normalizeBoolean(round)
        suppressError = normalizers.normalizeBoolean(suppressError)
        self._interpolate(factor, minFont, maxFont,
                          round=round, suppressError=suppressError)

    def _interpolate(self, factor, minFont, maxFont,
                     round=True, suppressError=True):
        """
        This is the environment implementation of
        :meth:`BaseFont.interpolate`.

        Subclasses may override this method.
        """
        # layers
        for layerName in self.layerOrder:
            self.removeLayer(layerName)
        for layerName in minFont.layerOrder:
            if layerName not in maxFont.layerOrder:
                continue
            minLayer = minFont.getLayer(layerName)
            maxLayer = maxFont.getLayer(layerName)
            dstLayer = self.newLayer(layerName)
            dstLayer.interpolate(factor, minLayer, maxLayer,
                                 round=round, suppressError=suppressError)
        if self.layerOrder:
            if ufoLib.DEFAULT_LAYER_NAME in self.layerOrder:
                self.defaultLayer = self.getLayer(ufoLib.DEFAULT_LAYER_NAME)
            else:
                self.defaultLayer = self.getLayer(self.layerOrder[0])
        # kerning and groups
        self.kerning.interpolate(factor, minFont.kerning, maxFont.kerning,
                                 round=round, suppressError=suppressError)
        # info
        self.info.interpolate(factor, minFont.info, maxFont.info,
                              round=round, suppressError=suppressError)

    compatibilityReporterClass = FontCompatibilityReporter

    def isCompatible(self, other):
        """
        Evaluate interpolation compatibility with **other**.

            >>> compatible, report = self.isCompatible(otherFont)
            >>> compatible
            False
            >>> report
            [Fatal] Glyph: "test1" + "test2"
            [Fatal] Glyph: "test1" contains 1 contours | "test2" contains 2 contours

        This will return a ``bool`` indicating if the font is
        compatible for interpolation with **other** and a
        :ref:`type-string` of compatibility notes.
        """
        return super(BaseFont, self).isCompatible(other, BaseFont)

    def _isCompatible(self, other, reporter):
        """
        This is the environment implementation of
        :meth:`BaseFont.isCompatible`.

        Subclasses may override this method.
        """
        font1 = self
        font2 = other

        # incompatible guidelines
        guidelines1 = set(font1.guidelines)
        guidelines2 = set(font2.guidelines)
        if len(guidelines1) != len(guidelines2):
            reporter.warning = True
            reporter.guidelineCountDifference = True
        if len(guidelines1.difference(guidelines2)) != 0:
            reporter.warning = True
            reporter.guidelinesMissingFromFont2 = list(
                guidelines1.difference(guidelines2))
        if len(guidelines2.difference(guidelines1)) != 0:
            reporter.warning = True
            reporter.guidelinesMissingInFont1 = list(
                guidelines2.difference(guidelines1))
        # incompatible layers
        layers1 = set(font1.layerOrder)
        layers2 = set(font2.layerOrder)
        if len(layers1) != len(layers2):
            reporter.warning = True
            reporter.layerCountDifference = True
        if len(layers1.difference(layers2)) != 0:
            reporter.warning = True
            reporter.layersMissingFromFont2 = list(layers1.difference(layers2))
        if len(layers2.difference(layers1)) != 0:
            reporter.warning = True
            reporter.layersMissingInFont1 = list(layers2.difference(layers1))
        # test layers
        for layerName in sorted(layers1.intersection(layers2)):
            layer1 = font1.getLayer(layerName)
            layer2 = font2.getLayer(layerName)
            layerCompatibility = layer1.isCompatible(layer2)[1]
            if layerCompatibility.fatal or layerCompatibility.warning:
                if layerCompatibility.fatal:
                    reporter.fatal = True
                if layerCompatibility.warning:
                    reporter.warning = True
                reporter.layers.append(layerCompatibility)

    # -------
    # mapping
    # -------

    def getReverseComponentMapping(self):
        """
        Get a reversed map of component references in the font.
        {
        'A' : ['Aacute', 'Aring']
        'acute' : ['Aacute']
        'ring' : ['Aring']
        etc.
        }
        """
        return self._getReverseComponentMapping()

    def _getReverseComponentMapping(self):
        """
        This is the environment implementation of
        :meth:`BaseFont.getReverseComponentMapping`.

        Subclasses may override this method.
        """
        layer = self.defaultLayer
        return layer.getReverseComponentMapping()

    def getCharacterMapping(self):
        """
        Create a dictionary of unicode -> [glyphname, ...] mappings.
        All glyphs are loaded. Note that one glyph can have multiple unicode values,
        and a unicode value can have multiple glyphs pointing to it.
        """
        return self._getCharacterMapping()

    def _getCharacterMapping(self):
        """
        This is the environment implementation of
        :meth:`BaseFont.getCharacterMapping`.

        Subclasses may override this method.
        """
        layer = self.defaultLayer
        return layer.getCharacterMapping()

    # ---------
    # Selection
    # ---------

    # layers

    selectedLayers = dynamicProperty(
        "base_selectedLayers",
        """
        A list of layers selected in the layer.

        Getting selected layer objects:

            >>> for layer in layer.selectedLayers:
            ...     layer.color = (1, 0, 0, 0.5)

        Setting selected layer objects:

            >>> layer.selectedLayers = someLayers
        """
    )

    def _get_base_selectedLayers(self):
        selected = tuple([normalizers.normalizeLayer(layer) for
                          layer in self._get_selectedLayers()])
        return selected

    def _get_selectedLayers(self):
        """
        Subclasses may override this method.
        """
        return self._getSelectedSubObjects(self.layers)

    def _set_base_selectedLayers(self, value):
        normalized = [normalizers.normalizeLayer(layer) for layer in value]
        self._set_selectedLayers(normalized)

    def _set_selectedLayers(self, value):
        """
        Subclasses may override this method.
        """
        return self._setSelectedSubObjects(self.layers, value)

    selectedLayerNames = dynamicProperty(
        "base_selectedLayerNames",
        """
        A list of names of layers selected in the layer.

        Getting selected layer names:

            >>> for name in layer.selectedLayerNames:
            ...     print(name)

        Setting selected layer names:

            >>> layer.selectedLayerNames = ["A", "B", "C"]
        """
    )

    def _get_base_selectedLayerNames(self):
        selected = tuple([normalizers.normalizeLayerName(name) for
                          name in self._get_selectedLayerNames()])
        return selected

    def _get_selectedLayerNames(self):
        """
        Subclasses may override this method.
        """
        selected = [layer.name for layer in self.selectedLayers]
        return selected

    def _set_base_selectedLayerNames(self, value):
        normalized = [normalizers.normalizeLayerName(name) for name in value]
        self._set_selectedLayerNames(normalized)

    def _set_selectedLayerNames(self, value):
        """
        Subclasses may override this method.
        """
        select = [self.layers(name) for name in value]
        self.selectedLayers = select

    # guidelines

    selectedGuidelines = dynamicProperty(
        "base_selectedGuidelines",
        """
        A list of guidelines selected in the font.

        Getting selected guideline objects:

            >>> for guideline in font.selectedGuidelines:
            ...     guideline.color = (1, 0, 0, 0.5)

        Setting selected guideline objects:

            >>> font.selectedGuidelines = someGuidelines

        Setting also supports guideline indexes:

            >>> font.selectedGuidelines = [0, 2]
        """
    )

    def _get_base_selectedGuidelines(self):
        selected = tuple([normalizers.normalizeGuideline(guideline) for
                          guideline in self._get_selectedGuidelines()])
        return selected

    def _get_selectedGuidelines(self):
        """
        Subclasses may override this method.
        """
        return self._getSelectedSubObjects(self.guidelines)

    def _set_base_selectedGuidelines(self, value):
        normalized = []
        for i in value:
            if isinstance(i, int):
                i = normalizers.normalizeIndex(i)
            else:
                i = normalizers.normalizeGuideline(i)
            normalized.append(i)
        self._set_selectedGuidelines(normalized)

    def _set_selectedGuidelines(self, value):
        """
        Subclasses may override this method.
        """
        return self._setSelectedSubObjects(self.guidelines, value)
