# Copyright 2010-2013 by Peter Cock.  All rights reserved.
# This code is part of the Biopython distribution and governed by its
# license.  Please see the LICENSE file that should have been included
# as part of this package.
"""Python 3 compatibility tools (PRIVATE).

We used to have lines like this under Python 2 in order to use
iterator based zip, map and filter (in Python 3 these functions
are all iterator based)::

    from future_builtins import zip

There is no similar option for range yet, other than::

    range = xrange
    input = raw_input

or:

    from __builtin__ import xrange as range
    from __builtin__ import raw_input as input

Under Python 3 these imports need to be removed. Also, deliberate
importing of built in functions like open changes from Python 2::

    from __builtin__ import open

To do this under Python 3:

    from builtins import open

Instead, we can do this under either Python 2 or 3:

    from Bio._py3k import open
    from Bio._py3k import zip

Once we drop support for Python 2, the whole of Bio._py3k will
go away.
"""
import sys


if sys.version_info[0] >= 3:
    # Code for Python 3
    from builtins import open, zip, map, filter, range, input

    import codecs

    # Lots of our Python 2 code uses isinstance(x, basestring)
    # which after 2to3 becomes isinstance(x, str)
    basestring = str
    unicode = str

    _bytes_to_string = lambda b: b.decode()  # bytes to unicode string
    _string_to_bytes = lambda s: s.encode()  # unicode string to bytes

    def _bytes_bytearray_to_str(s):
        """If s is bytes or bytearray, convert to a unicode string."""
        if isinstance(s, (bytes, bytearray)):
            return s.decode()
        return s

    def _as_unicode(s):
        """Turn byte string or unicode string into a unicode string."""
        if isinstance(s, str):
            return s
        # Assume it is a bytes string
        # Note ISO-8859-1 aka Latin-1 preserves first 256 chars
        return codecs.latin_1_decode(s)[0]

    def _as_bytes(s):
        """Turn byte string or unicode string into a bytes string.

        The Python 2 version returns a (byte) string.
        """
        if isinstance(s, bytes):
            return s
        # Assume it is a unicode string
        # Note ISO-8859-1 aka Latin-1 preserves first 256 chars
        return codecs.latin_1_encode(s)[0]

    _as_string = _as_unicode

    def _is_int_or_long(i):
        """Check if the value is an integer.

        Note there are no longs on Python 3.
        """
        return isinstance(i, int)

    import io

    if sys.version_info[:2] <= (3, 3):
        def _binary_to_string_handle(handle):
            """Treat a binary (bytes) handle like a text (unicode) handle."""
            # TODO, once drop all of Python 3.0 - 3.3, remove this!
            #
            # See also http://bugs.python.org/issue5628
            # and http://bugs.python.org/issue13541
            # and http://bugs.python.org/issue13464 which should be fixed in Python 3.3
            #
            # However, still have problems under Python 3.3.0, e.g.
            #
            # $ python3.3 test_SeqIO_online.py
            # test_nuccore_X52960 (__main__.EntrezTests)
            # Bio.Entrez.efetch('nuccore', id='X52960', ...) ... ERROR
            # test_nucleotide_6273291 (__main__.EntrezTests)
            # Bio.Entrez.efetch('nucleotide', id='6273291', ...) ... ERROR
            # test_protein_16130152 (__main__.EntrezTests)
            # Bio.Entrez.efetch('protein', id='16130152', ...) ... ERROR
            # test_get_sprot_raw (__main__.ExPASyTests)
            # Bio.ExPASy.get_sprot_raw("O23729") ... ok
            # ..
            # ValueError: I/O operation on closed file.
            #
            class EvilHandleHack(object):
                """Biopython internal class to work around bugs in early versions of Python 3."""
                def __init__(self, handle):
                    self._handle = handle
                    try:
                        # If wrapping an online handle, this this is nice to have:
                        self.url = handle.url
                    except AttributeError:
                        pass

                def read(self, length=None):
                    return _as_string(self._handle.read(length))

                def readline(self):
                    return _as_string(self._handle.readline())

                def __iter__(self):
                    for line in self._handle:
                        yield _as_string(line)

                def close(self):
                    return self._handle.close()

                def seek(self, pos):
                    return self._handle.seek(pos)

                def tell(self):
                    return self._handle.tell()

            return EvilHandleHack(handle)
    else:
        # Python 3.4 onwards, the standard library wrappers should work:
        def _binary_to_string_handle(handle):
            """Treat a binary (bytes) handle like a text (unicode) handle."""
            wrapped = io.TextIOWrapper(io.BufferedReader(handle))
            try:
                # If wrapping an online handle, this this is nice to have:
                wrapped.url = handle.url
            except AttributeError:
                pass
            return wrapped

    # This is to avoid the deprecation warning from open(filename, "rU")
    _universal_read_mode = "r"  # text mode does universal new lines

    # On Python 3, can depend on OrderedDict being present:
    from collections import OrderedDict

    # On Python 3, this will be a unicode StringIO
    from io import StringIO

    # On Python 3 urllib, urllib2, and urlparse were merged:
    from urllib.request import urlopen, Request, urlretrieve, urlparse
    from urllib.parse import urlencode, quote
    from urllib.error import HTTPError

else:
    # Python 2 code
    from __builtin__ import open, basestring, unicode

    # Import Python3 like iterator functions:
    from future_builtins import zip, map, filter
    from __builtin__ import xrange as range
    from __builtin__ import raw_input as input

    _bytes_to_string = lambda b: b  # bytes to string, i.e. do nothing
    _string_to_bytes = lambda s: str(s)  # str (or unicode) to bytes string

    def _bytes_bytearray_to_str(s):
        """If s is bytes or bytearray, convert to a string."""
        if isinstance(s, (bytes, bytearray)):
            return str(s)
        return s

    def _as_unicode(s):
        """Turn a (byte) string or a unicode string into a (byte) string."""
        # Will be changed by 2to3 to "isinstance(s, str)" but doesn't matter:
        if isinstance(s, unicode):
            return s
        return s.decode()

    def _as_bytes(s):
        """Turn a (byte) string or a unicode string into a (byte) string."""
        return str(s)

    _as_string = _as_bytes

    def _is_int_or_long(i):
        """Check if the value is an integer or long."""
        return isinstance(i, (int, long))

    def _binary_to_string_handle(handle):
        """Treat a binary handle like a text handle."""
        return handle

    # This private variable is set to "r" on Python 3 for text
    # mode which include universal readlines mode
    _universal_read_mode = "rU"

    try:
        # Present on Python 2.7
        from collections import OrderedDict
    except ImportError:
        try:
            # Raymond Hettinger's backport available on PyPI
            from ordereddict import OrderedDict
        except ImportError:
            # Use our bundled copy instead
            from ._ordereddict import OrderedDict

    # On Python 2 this will be a (bytes) string based handle.
    # Note this doesn't work as it is unicode based:
    # from io import StringIO
    try:
        from cStringIO import StringIO
    except ImportError:
        from StringIO import StringIO

    # Under urllib.request on Python 3:
    from urllib2 import urlopen, Request
    from urllib import urlretrieve
    from urlparse import urlparse

    # Under urllib.parse on Python 3:
    from urllib import urlencode, quote

    # Under urllib.error on Python 3:
    from urllib2 import HTTPError


if sys.platform == "win32":
    # Can't use commands.getoutput on Python 2, Unix only/broken:
    # http://bugs.python.org/issue15073
    # Can't use subprocess.getoutput on Python 3, Unix only/broken:
    # http://bugs.python.org/issue10197
    def getoutput(cmd):
        import subprocess
        child = subprocess.Popen(cmd,
                                 stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT,
                                 universal_newlines=True,
                                 shell=False)
        stdout, stderr = child.communicate()
        # Remove trailing \n to match the Unix function,
        return stdout.rstrip("\n")
elif sys.version_info[0] >= 3:
    # Use subprocess.getoutput on Python 3,
    from subprocess import getoutput
else:
    # Use commands.getoutput on Python 2,
    from commands import getoutput
