"""Compatibility code for using Cheroot with various versions of Python.

Cheroot is compatible with Python versions 2.6+. This module provides a
useful abstraction over the differences between Python versions, sometimes by
preferring a newer idiom, sometimes an older one, and sometimes a custom one.
In particular, Python 2 uses str and '' for byte strings, while Python 3
uses str and '' for unicode strings. Refer to each of these the 'native
string' type for each version. Because of this major difference, this module
provides
two functions: 'ntob', which translates native strings (of type 'str') into
byte strings regardless of Python version, and 'ntou', which translates native
strings to unicode strings. This also provides a 'BytesIO' name for dealing
specifically with bytes, and a 'StringIO' name for dealing with native strings.
It also provides a 'base64_decode' function with native strings as input and
output.
"""

import binascii
import os
import re
import sys
import threading

import six

if six.PY3:
    def ntob(n, encoding='ISO-8859-1'):
        """Return the given native string as a byte string in the given encoding."""
        assert_native(n)
        # In Python 3, the native string type is unicode
        return n.encode(encoding)

    def ntou(n, encoding='ISO-8859-1'):
        """Return the given native string as a unicode string with the given encoding."""
        assert_native(n)
        # In Python 3, the native string type is unicode
        return n

    def tonative(n, encoding='ISO-8859-1'):
        """Return the given string as a native string in the given encoding."""
        # In Python 3, the native string type is unicode
        if isinstance(n, bytes):
            return n.decode(encoding)
        return n

    def bton(b, encoding='ISO-8859-1'):
        """Return the given byte string as a native string in the given encoding."""
        return b.decode(encoding)
else:
    # Python 2
    def ntob(n, encoding='ISO-8859-1'):
        """Return the given native string as a byte string in the given encoding."""
        assert_native(n)
        # In Python 2, the native string type is bytes. Assume it's already
        # in the given encoding, which for ISO-8859-1 is almost always what
        # was intended.
        return n

    def ntou(n, encoding='ISO-8859-1'):
        """Return the given native string as a unicode string with the given encoding."""
        assert_native(n)
        # In Python 2, the native string type is bytes.
        # First, check for the special encoding 'escape'. The test suite uses
        # this to signal that it wants to pass a string with embedded \uXXXX
        # escapes, but without having to prefix it with u'' for Python 2,
        # but no prefix for Python 3.
        if encoding == 'escape':
            return six.u(
                re.sub(r'\\u([0-9a-zA-Z]{4})',
                       lambda m: six.unichr(int(m.group(1), 16)),
                       n.decode('ISO-8859-1')))
        # Assume it's already in the given encoding, which for ISO-8859-1
        # is almost always what was intended.
        return n.decode(encoding)

    def tonative(n, encoding='ISO-8859-1'):
        """Return the given string as a native string in the given encoding."""
        # In Python 2, the native string type is bytes.
        if isinstance(n, six.text_type):
            return n.encode(encoding)
        return n

    def bton(b, encoding='ISO-8859-1'):
        """Return the given byte string as a native string in the given encoding."""
        return b


def assert_native(n):
    """Check whether the input is of nativ ``str`` type.

    Raises:
        TypeError: in case of failed check
    """
    if not isinstance(n, str):
        raise TypeError('n must be a native str (got %s)' % type(n).__name__)


try:
    # Python 3.1+
    from base64 import decodebytes as _base64_decodebytes
except ImportError:
    # Python 3.0-
    # since Cheroot claims compability with Python 2.6+, we must use
    # the legacy API of base64
    from base64 import decodestring as _base64_decodebytes


def base64_decode(n, encoding='ISO-8859-1'):
    """Return the native string base64-decoded (as a native string)."""
    if isinstance(n, six.text_type):
        b = n.encode(encoding)
    else:
        b = n
    b = _base64_decodebytes(b)
    if str is six.text_type:
        return b.decode(encoding)
    else:
        return b


try:
    sorted = sorted
except NameError:
    def sorted(i):
        """Return sorted sequence."""
        i = i[:]
        i.sort()
        return i

try:
    reversed = reversed
except NameError:
    def reversed(x):
        """Return sequence in reversed order."""
        i = len(x)
        while i > 0:
            i -= 1
            yield x[i]

from six.moves.urllib.parse import (  # noqa: F401
    quote, quote_plus,
    unquote as parse_unquote,
    urlencode, urljoin
)
from six.moves.urllib.request import urlopen  # noqa: F401
# TODO: uncomment this once new six released
# Ref: https://github.com/benjaminp/six/pull/172
# from six.moves.urllib.parse import unquote_to_bytes
if six.PY3:
    from urllib.parse import unquote_to_bytes
    from urllib.request import parse_http_list, parse_keqv_list
else:
    from urllib import unquote as unquote_to_bytes  # noqa
    from urllib2 import parse_http_list, parse_keqv_list  # noqa

try:
    dict.iteritems
    # Python 2
    iteritems = lambda d: d.iteritems()
    copyitems = lambda d: d.items()
except AttributeError:
    # Python 3
    iteritems = lambda d: d.items()
    copyitems = lambda d: list(d.items())

try:
    dict.iterkeys
    # Python 2
    iterkeys = lambda d: d.iterkeys()
    copykeys = lambda d: d.keys()
except AttributeError:
    # Python 3
    iterkeys = lambda d: d.keys()
    copykeys = lambda d: list(d.keys())

try:
    dict.itervalues
    # Python 2
    itervalues = lambda d: d.itervalues()
    copyvalues = lambda d: d.values()
except AttributeError:
    # Python 3
    itervalues = lambda d: d.values()
    copyvalues = lambda d: list(d.values())

try:
    # Python 3
    import builtins
except ImportError:
    # Python 2
    import __builtin__ as builtins  # noqa

try:
    # Python 2. We try Python 2 first clients on Python 2
    # don't try to import the 'http' module from cheroot submodules directly
    from Cookie import SimpleCookie, CookieError
    from httplib import BadStatusLine, HTTPConnection, IncompleteRead
    from httplib import NotConnected
    from BaseHTTPServer import BaseHTTPRequestHandler
except ImportError:
    # Python 3
    from http.cookies import SimpleCookie, CookieError  # noqa
    from http.client import BadStatusLine, HTTPConnection, IncompleteRead  # noqa
    from http.client import NotConnected  # noqa
    from http.server import BaseHTTPRequestHandler  # noqa

# Some platforms don't expose HTTPSConnection, so handle it separately
if six.PY3:
    try:
        from http.client import HTTPSConnection
    except ImportError:
        # Some platforms which don't have SSL don't expose HTTPSConnection
        HTTPSConnection = None
else:
    try:
        from httplib import HTTPSConnection
    except ImportError:
        HTTPSConnection = None

try:
    # Python 2
    xrange = xrange
except NameError:
    # Python 3
    xrange = range

if six.PY3:
    def unquote_qs(atom, encoding, errors='strict'):
        """Return urldecoded query string."""
        return parse_unquote(
            atom.replace('+', ' '),
            encoding=encoding,
            errors=errors)
else:
    def unquote_qs(atom, encoding, errors='strict'):
        """Return urldecoded query string."""
        return parse_unquote(atom.replace('+', ' ')).decode(encoding, errors)

try:
    # Prefer simplejson, which is usually more advanced than the builtin
    # module.
    import simplejson as json
    json_decode = json.JSONDecoder().decode
    _json_encode = json.JSONEncoder().iterencode
except ImportError:
    if sys.version_info >= (2, 6):
        # Python >=2.6 : json is part of the standard library
        import json
        json_decode = json.JSONDecoder().decode
        _json_encode = json.JSONEncoder().iterencode
    else:
        json = None

        def json_decode(s):
            """Alert that decoding JSON is not supported because of missing package."""
            raise ValueError('No JSON library is available')

        def _json_encode(s):
            """Alert that encoding JSON is not supported because of missing package."""
            raise ValueError('No JSON library is available')
finally:
    if json and six.PY3:
        # The two Python 3 implementations (simplejson/json)
        # outputs str. We need bytes.
        def json_encode(value):
            """Return JSON string as bytes."""
            for chunk in _json_encode(value):
                yield chunk.encode('utf8')
    else:
        json_encode = _json_encode

text_or_bytes = six.text_type, six.binary_type

try:
    import cPickle as pickle
except ImportError:
    # In Python 2, pickle is a Python version.
    # In Python 3, pickle is the sped-up C version.
    import pickle  # noqa


def random20():
    """Return a random string of 20 bytes."""
    return binascii.hexlify(os.urandom(20)).decode('ascii')


try:
    from _thread import get_ident as get_thread_ident
except ImportError:
    from thread import get_ident as get_thread_ident  # noqa

try:
    # Python 3
    next = next
except NameError:
    # Python 2
    def next(i):
        """Return the next value of an iterator."""
        return i.next()

if sys.version_info >= (3, 3):
    Timer = threading.Timer
    Event = threading.Event
else:
    # Python 3.2 and earlier
    Timer = threading._Timer
    Event = threading._Event

try:
    # Python 2.7+
    from subprocess import _args_from_interpreter_flags
except ImportError:
    def _args_from_interpreter_flags():
        """Try to reconstruct original interpreter args from sys.flags for Python 2.6.

        Backported from Python 3.5. Aims to return a list of
        command-line arguments reproducing the current
        settings in sys.flags and sys.warnoptions.
        """
        flag_opt_map = {
            'debug': 'd',
            # 'inspect': 'i',
            # 'interactive': 'i',
            'optimize': 'O',
            'dont_write_bytecode': 'B',
            'no_user_site': 's',
            'no_site': 'S',
            'ignore_environment': 'E',
            'verbose': 'v',
            'bytes_warning': 'b',
            'quiet': 'q',
            'hash_randomization': 'R',
            'py3k_warning': '3',
        }

        args = []
        for flag, opt in flag_opt_map.items():
            v = getattr(sys.flags, flag)
            if v > 0:
                if flag == 'hash_randomization':
                    v = 1  # Handle specification of an exact seed
                args.append('-' + opt * v)
        for opt in sys.warnoptions:
            args.append('-W' + opt)

        return args

# html module come in 3.2 version
try:
    from html import escape
except ImportError:
    from cgi import escape


# html module needed the argument quote=False because in cgi the default
# is False. With quote=True the results differ.

def escape_html(s, escape_quote=False):
    """Replace special characters "&", "<" and ">" to HTML-safe sequences.

    When escape_quote=True, escape (') and (") chars.
    """
    return escape(s, quote=escape_quote)
