# -*- coding: utf-8 -*-
__all__ = ['Distribution']

import io
import sys
import re
import os
import warnings
import numbers
import distutils.log
import distutils.core
import distutils.cmd
import distutils.dist
from distutils.util import strtobool
from distutils.debug import DEBUG
from distutils.fancy_getopt import translate_longopt
import itertools

from collections import defaultdict
from email import message_from_file

from distutils.errors import (
    DistutilsOptionError, DistutilsPlatformError, DistutilsSetupError,
)
from distutils.util import rfc822_escape
from distutils.version import StrictVersion

if "__PEX_UNVENDORED__" in __import__("os").environ:
  from setuptools.extern import six  # vendor:skip
else:
  from pex.third_party.setuptools.extern import six

if "__PEX_UNVENDORED__" in __import__("os").environ:
  from setuptools.extern import packaging  # vendor:skip
else:
  from pex.third_party.setuptools.extern import packaging

if "__PEX_UNVENDORED__" in __import__("os").environ:
  from setuptools.extern import ordered_set  # vendor:skip
else:
  from pex.third_party.setuptools.extern import ordered_set

if "__PEX_UNVENDORED__" in __import__("os").environ:
  from setuptools.extern.six.moves import map, filter, filterfalse  # vendor:skip
else:
  from pex.third_party.setuptools.extern.six.moves import map, filter, filterfalse


from . import SetuptoolsDeprecationWarning

if "__PEX_UNVENDORED__" in __import__("os").environ:
  from setuptools.depends import Require  # vendor:skip
else:
  from pex.third_party.setuptools.depends import Require

if "__PEX_UNVENDORED__" in __import__("os").environ:
  from setuptools import windows_support  # vendor:skip
else:
  from pex.third_party.setuptools import windows_support

if "__PEX_UNVENDORED__" in __import__("os").environ:
  from setuptools.monkey import get_unpatched  # vendor:skip
else:
  from pex.third_party.setuptools.monkey import get_unpatched

if "__PEX_UNVENDORED__" in __import__("os").environ:
  from setuptools.config import parse_configuration  # vendor:skip
else:
  from pex.third_party.setuptools.config import parse_configuration

if "__PEX_UNVENDORED__" in __import__("os").environ:
  import pkg_resources  # vendor:skip
else:
  import pex.third_party.pkg_resources as pkg_resources


if "__PEX_UNVENDORED__" in __import__("os").environ:
  __import__('setuptools.extern.packaging.specifiers')  # vendor:skip
else:
  __import__('pex.third_party.setuptools.extern.packaging.specifiers')

if "__PEX_UNVENDORED__" in __import__("os").environ:
  __import__('setuptools.extern.packaging.version')  # vendor:skip
else:
  __import__('pex.third_party.setuptools.extern.packaging.version')



def _get_unpatched(cls):
    warnings.warn("Do not call this function", DistDeprecationWarning)
    return get_unpatched(cls)


def get_metadata_version(self):
    mv = getattr(self, 'metadata_version', None)

    if mv is None:
        if self.long_description_content_type or self.provides_extras:
            mv = StrictVersion('2.1')
        elif (self.maintainer is not None or
              self.maintainer_email is not None or
              getattr(self, 'python_requires', None) is not None or
              self.project_urls):
            mv = StrictVersion('1.2')
        elif (self.provides or self.requires or self.obsoletes or
                self.classifiers or self.download_url):
            mv = StrictVersion('1.1')
        else:
            mv = StrictVersion('1.0')

        self.metadata_version = mv

    return mv


def read_pkg_file(self, file):
    """Reads the metadata values from a file object."""
    msg = message_from_file(file)

    def _read_field(name):
        value = msg[name]
        if value == 'UNKNOWN':
            return None
        return value

    def _read_list(name):
        values = msg.get_all(name, None)
        if values == []:
            return None
        return values

    self.metadata_version = StrictVersion(msg['metadata-version'])
    self.name = _read_field('name')
    self.version = _read_field('version')
    self.description = _read_field('summary')
    # we are filling author only.
    self.author = _read_field('author')
    self.maintainer = None
    self.author_email = _read_field('author-email')
    self.maintainer_email = None
    self.url = _read_field('home-page')
    self.license = _read_field('license')

    if 'download-url' in msg:
        self.download_url = _read_field('download-url')
    else:
        self.download_url = None

    self.long_description = _read_field('description')
    self.description = _read_field('summary')

    if 'keywords' in msg:
        self.keywords = _read_field('keywords').split(',')

    self.platforms = _read_list('platform')
    self.classifiers = _read_list('classifier')

    # PEP 314 - these fields only exist in 1.1
    if self.metadata_version == StrictVersion('1.1'):
        self.requires = _read_list('requires')
        self.provides = _read_list('provides')
        self.obsoletes = _read_list('obsoletes')
    else:
        self.requires = None
        self.provides = None
        self.obsoletes = None


# Based on Python 3.5 version
def write_pkg_file(self, file):
    """Write the PKG-INFO format data to a file object.
    """
    version = self.get_metadata_version()

    if six.PY2:
        def write_field(key, value):
            file.write("%s: %s\n" % (key, self._encode_field(value)))
    else:
        def write_field(key, value):
            file.write("%s: %s\n" % (key, value))

    write_field('Metadata-Version', str(version))
    write_field('Name', self.get_name())
    write_field('Version', self.get_version())
    write_field('Summary', self.get_description())
    write_field('Home-page', self.get_url())

    if version < StrictVersion('1.2'):
        write_field('Author', self.get_contact())
        write_field('Author-email', self.get_contact_email())
    else:
        optional_fields = (
            ('Author', 'author'),
            ('Author-email', 'author_email'),
            ('Maintainer', 'maintainer'),
            ('Maintainer-email', 'maintainer_email'),
        )

        for field, attr in optional_fields:
            attr_val = getattr(self, attr)

            if attr_val is not None:
                write_field(field, attr_val)

    write_field('License', self.get_license())
    if self.download_url:
        write_field('Download-URL', self.download_url)
    for project_url in self.project_urls.items():
        write_field('Project-URL',  '%s, %s' % project_url)

    long_desc = rfc822_escape(self.get_long_description())
    write_field('Description', long_desc)

    keywords = ','.join(self.get_keywords())
    if keywords:
        write_field('Keywords', keywords)

    if version >= StrictVersion('1.2'):
        for platform in self.get_platforms():
            write_field('Platform', platform)
    else:
        self._write_list(file, 'Platform', self.get_platforms())

    self._write_list(file, 'Classifier', self.get_classifiers())

    # PEP 314
    self._write_list(file, 'Requires', self.get_requires())
    self._write_list(file, 'Provides', self.get_provides())
    self._write_list(file, 'Obsoletes', self.get_obsoletes())

    # Setuptools specific for PEP 345
    if hasattr(self, 'python_requires'):
        write_field('Requires-Python', self.python_requires)

    # PEP 566
    if self.long_description_content_type:
        write_field(
            'Description-Content-Type',
            self.long_description_content_type
        )
    if self.provides_extras:
        for extra in self.provides_extras:
            write_field('Provides-Extra', extra)


sequence = tuple, list


def check_importable(dist, attr, value):
    try:
        ep = pkg_resources.EntryPoint.parse('x=' + value)
        assert not ep.extras
    except (TypeError, ValueError, AttributeError, AssertionError):
        raise DistutilsSetupError(
            "%r must be importable 'module:attrs' string (got %r)"
            % (attr, value)
        )


def assert_string_list(dist, attr, value):
    """Verify that value is a string list"""
    try:
        # verify that value is a list or tuple to exclude unordered
        # or single-use iterables
        assert isinstance(value, (list, tuple))
        # verify that elements of value are strings
        assert ''.join(value) != value
    except (TypeError, ValueError, AttributeError, AssertionError):
        raise DistutilsSetupError(
            "%r must be a list of strings (got %r)" % (attr, value)
        )


def check_nsp(dist, attr, value):
    """Verify that namespace packages are valid"""
    ns_packages = value
    assert_string_list(dist, attr, ns_packages)
    for nsp in ns_packages:
        if not dist.has_contents_for(nsp):
            raise DistutilsSetupError(
                "Distribution contains no modules or packages for " +
                "namespace package %r" % nsp
            )
        parent, sep, child = nsp.rpartition('.')
        if parent and parent not in ns_packages:
            distutils.log.warn(
                "WARNING: %r is declared as a package namespace, but %r"
                " is not: please correct this in setup.py", nsp, parent
            )


def check_extras(dist, attr, value):
    """Verify that extras_require mapping is valid"""
    try:
        list(itertools.starmap(_check_extra, value.items()))
    except (TypeError, ValueError, AttributeError):
        raise DistutilsSetupError(
            "'extras_require' must be a dictionary whose values are "
            "strings or lists of strings containing valid project/version "
            "requirement specifiers."
        )


def _check_extra(extra, reqs):
    name, sep, marker = extra.partition(':')
    if marker and pkg_resources.invalid_marker(marker):
        raise DistutilsSetupError("Invalid environment marker: " + marker)
    list(pkg_resources.parse_requirements(reqs))


def assert_bool(dist, attr, value):
    """Verify that value is True, False, 0, or 1"""
    if bool(value) != value:
        tmpl = "{attr!r} must be a boolean value (got {value!r})"
        raise DistutilsSetupError(tmpl.format(attr=attr, value=value))


def check_requirements(dist, attr, value):
    """Verify that install_requires is a valid requirements list"""
    try:
        list(pkg_resources.parse_requirements(value))
        if isinstance(value, (dict, set)):
            raise TypeError("Unordered types are not allowed")
    except (TypeError, ValueError) as error:
        tmpl = (
            "{attr!r} must be a string or list of strings "
            "containing valid project/version requirement specifiers; {error}"
        )
        raise DistutilsSetupError(tmpl.format(attr=attr, error=error))


def check_specifier(dist, attr, value):
    """Verify that value is a valid version specifier"""
    try:
        packaging.specifiers.SpecifierSet(value)
    except packaging.specifiers.InvalidSpecifier as error:
        tmpl = (
            "{attr!r} must be a string "
            "containing valid version specifiers; {error}"
        )
        raise DistutilsSetupError(tmpl.format(attr=attr, error=error))


def check_entry_points(dist, attr, value):
    """Verify that entry_points map is parseable"""
    try:
        pkg_resources.EntryPoint.parse_map(value)
    except ValueError as e:
        raise DistutilsSetupError(e)


def check_test_suite(dist, attr, value):
    if not isinstance(value, six.string_types):
        raise DistutilsSetupError("test_suite must be a string")


def check_package_data(dist, attr, value):
    """Verify that value is a dictionary of package names to glob lists"""
    if not isinstance(value, dict):
        raise DistutilsSetupError(
            "{!r} must be a dictionary mapping package names to lists of "
            "string wildcard patterns".format(attr))
    for k, v in value.items():
        if not isinstance(k, six.string_types):
            raise DistutilsSetupError(
                "keys of {!r} dict must be strings (got {!r})"
                .format(attr, k)
            )
        assert_string_list(dist, 'values of {!r} dict'.format(attr), v)


def check_packages(dist, attr, value):
    for pkgname in value:
        if not re.match(r'\w+(\.\w+)*', pkgname):
            distutils.log.warn(
                "WARNING: %r not a valid package name; please use only "
                ".-separated package names in setup.py", pkgname
            )


_Distribution = get_unpatched(distutils.core.Distribution)


class Distribution(_Distribution):
    """Distribution with support for features, tests, and package data

    This is an enhanced version of 'distutils.dist.Distribution' that
    effectively adds the following new optional keyword arguments to 'setup()':

     'install_requires' -- a string or sequence of strings specifying project
        versions that the distribution requires when installed, in the format
        used by 'pkg_resources.require()'.  They will be installed
        automatically when the package is installed.  If you wish to use
        packages that are not available in PyPI, or want to give your users an
        alternate download location, you can add a 'find_links' option to the
        '[easy_install]' section of your project's 'setup.cfg' file, and then
        setuptools will scan the listed web pages for links that satisfy the
        requirements.

     'extras_require' -- a dictionary mapping names of optional "extras" to the
        additional requirement(s) that using those extras incurs. For example,
        this::

            extras_require = dict(reST = ["docutils>=0.3", "reSTedit"])

        indicates that the distribution can optionally provide an extra
        capability called "reST", but it can only be used if docutils and
        reSTedit are installed.  If the user installs your package using
        EasyInstall and requests one of your extras, the corresponding
        additional requirements will be installed if needed.

     'features' **deprecated** -- a dictionary mapping option names to
        'setuptools.Feature'
        objects.  Features are a portion of the distribution that can be
        included or excluded based on user options, inter-feature dependencies,
        and availability on the current system.  Excluded features are omitted
        from all setup commands, including source and binary distributions, so
        you can create multiple distributions from the same source tree.
        Feature names should be valid Python identifiers, except that they may
        contain the '-' (minus) sign.  Features can be included or excluded
        via the command line options '--with-X' and '--without-X', where 'X' is
        the name of the feature.  Whether a feature is included by default, and
        whether you are allowed to control this from the command line, is
        determined by the Feature object.  See the 'Feature' class for more
        information.

     'test_suite' -- the name of a test suite to run for the 'test' command.
        If the user runs 'python setup.py test', the package will be installed,
        and the named test suite will be run.  The format is the same as
        would be used on a 'unittest.py' command line.  That is, it is the
        dotted name of an object to import and call to generate a test suite.

     'package_data' -- a dictionary mapping package names to lists of filenames
        or globs to use to find data files contained in the named packages.
        If the dictionary has filenames or globs listed under '""' (the empty
        string), those names will be searched for in every package, in addition
        to any names for the specific package.  Data files found using these
        names/globs will be installed along with the package, in the same
        location as the package.  Note that globs are allowed to reference
        the contents of non-package subdirectories, as long as you use '/' as
        a path separator.  (Globs are automatically converted to
        platform-specific paths at runtime.)

    In addition to these new keywords, this class also has several new methods
    for manipulating the distribution's contents.  For example, the 'include()'
    and 'exclude()' methods can be thought of as in-place add and subtract
    commands that add or remove packages, modules, extensions, and so on from
    the distribution.  They are used by the feature subsystem to configure the
    distribution for the included and excluded features.
    """

    _DISTUTILS_UNSUPPORTED_METADATA = {
        'long_description_content_type': None,
        'project_urls': dict,
        'provides_extras': ordered_set.OrderedSet,
        'license_files': ordered_set.OrderedSet,
    }

    _patched_dist = None

    def patch_missing_pkg_info(self, attrs):
        # Fake up a replacement for the data that would normally come from
        # PKG-INFO, but which might not yet be built if this is a fresh
        # checkout.
        #
        if not attrs or 'name' not in attrs or 'version' not in attrs:
            return
        key = pkg_resources.safe_name(str(attrs['name'])).lower()
        dist = pkg_resources.working_set.by_key.get(key)