# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

"""PEP425 handling for pex

PEP425 (http://legacy.python.org/dev/peps/pep-0425/) describes a tagging system used to determine
whether or not a distribution's platform is compatible with the current platform.  It is the
tagging system used to describe platform compatibility for wheel files.
"""

from pkg_resources import get_supported_platform

from .platforms import Platform


class PEP425Extras(object):
  """Extensions to platform handling beyond PEP425."""

  @classmethod
  def is_macosx_platform(cls, platform):
    return platform.startswith('macosx')

  @classmethod
  def parse_macosx_tag(cls, platform_tag):
    invalid_tag = ValueError('invalid macosx tag: %s' % platform_tag)
    if not cls.is_macosx_platform(platform_tag):
      raise invalid_tag
    segments = platform_tag.split('_', 3)
    if len(segments) != 4:
      raise invalid_tag
    if segments[0] != 'macosx':
      raise invalid_tag
    try:
      major, minor = int(segments[1]), int(segments[2])
      platform = segments[3]
    except ValueError:
      raise invalid_tag
    return major, minor, platform

  @classmethod
  def iter_compatible_osx_platforms(cls, supported_platform):
    platform_major, platform_minor, platform = cls.parse_macosx_tag(supported_platform)
    platform_equivalents = set(Platform.MACOSX_PLATFORM_COMPATIBILITY.get(platform, ()))
    platform_equivalents.add(platform)
    for minor in range(platform_minor, -1, -1):
      for binary_compat in platform_equivalents:
        yield 'macosx_%s_%s_%s' % (platform_major, minor, binary_compat)

  @classmethod
  def platform_iterator(cls, platform):
    """Iterate over all compatible platform tags of a supplied platform tag.

       :param platform: the platform tag to iterate over
    """
    if cls.is_macosx_platform(platform):
      for plat in cls.iter_compatible_osx_platforms(platform):
        yield plat
    else:
      yield platform


class PEP425(object):  # noqa
  INTERPRETER_TAGS = {
    'CPython': 'cp',
    'Jython': 'jy',
    'PyPy': 'pp',
    'IronPython': 'ip',
  }

  @classmethod
  def get_implementation_tag(cls, interpreter_subversion):
    return cls.INTERPRETER_TAGS.get(interpreter_subversion)

  @classmethod
  def get_version_tag(cls, interpreter_version):
    return ''.join(map(str, interpreter_version[:2]))

  @classmethod
  def translate_platform_to_tag(cls, platform):
    return platform.replace('.', '_').replace('-', '_')

  @classmethod
  def get_platform_tag(cls):
    return cls.translate_platform_to_tag(get_supported_platform())

  # TODO(wickman) This implementation is technically incorrect but we need to be able to
  # predict the supported tags of an interpreter that may not be on this machine or
  # of a different platform.  Alternatively we could store the manifest of supported tags
  # of a targeted platform in a file to be more correct.
  @classmethod
  def _iter_supported_tags(cls, impl, version, platform):
    """Given a set of tags, iterate over supported tags.

    :param impl: Python implementation tag e.g. cp, jy, pp.
    :param version: E.g. '26', '33'
    :param platform: Platform as from :function:`pkg_resources.get_supported_platform`,
    for example 'linux-x86_64' or 'macosx-10.4-x86_64'.
    :returns: Iterator over (pyver, abi, platform) tuples.
    """
    # Predict soabi for reasonable interpreters.  This is technically wrong but essentially right.
    abis = []
    if impl == 'cp' and (version.startswith('2') or version.startswith('3')):
      abis.extend([
        'cp%s' % version,
        'cp%sdmu' % version, 'cp%sdm' % version, 'cp%sdu' % version, 'cp%sd' % version,
        'cp%smu' % version, 'cp%sm' % version,
        'cp%su' % version
      ])

      if version.startswith('3'):
        abis.extend([
          'abi3'
        ])

    major_version = int(version[0])
    minor_versions = []
    for minor in range(int(version[1]), -1, -1):
      minor_versions.append('%d%d' % (major_version, minor))

    platforms = list(PEP425Extras.platform_iterator(cls.translate_platform_to_tag(platform)))

    # interpreter specific
    for p in platforms:
      for abi in abis:
        yield ('%s%s' % (impl, version), abi, p)

    # everything else
    for p in platforms + ['any']:
      for i in ('py', impl):
        yield ('%s%d' % (i, major_version), 'none', p)
        for minor_version in minor_versions:
          yield ('%s%s' % (i, minor_version), 'none', p)

  @classmethod
  def iter_supported_tags(cls, identity, platform=get_supported_platform()):
    """Iterate over the supported tag tuples of this interpreter.

    :param identity: python interpreter identity over which tags should iterate.
    :type identity: :class:`PythonIdentity`
    :param platform: python platform over which tags should iterate, by default the current
                     platform.
    :returns: Iterator over valid PEP425 tag tuples.
    """
    impl_tag = cls.get_implementation_tag(identity.interpreter)
    vers_tag = cls.get_version_tag(identity.version)
    tag_iterator = cls._iter_supported_tags(impl_tag, vers_tag, platform)
    for tag in tag_iterator:
      yield tag
