# -*- coding: utf-8 -*-
# Copyright (C) 2012 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import absolute_import, division, print_function, unicode_literals

from functools import reduce
from logging import getLogger
import os
from os.path import abspath, basename, expanduser, expandvars, join, normcase, split, splitext
import re
import subprocess

from .compat import PY2, ensure_fs_path_encoding, on_win, string_types
from .. import CondaError
from .._vendor.auxlib.decorators import memoize
from .._vendor.toolz import accumulate, concat, take

try:
    # Python 3
    from urllib.parse import unquote, urlsplit
except ImportError:  # pragma: no cover
    # Python 2
    from urllib import unquote  # NOQA
    from urlparse import urlsplit  # NOQA

log = getLogger(__name__)

PATH_MATCH_REGEX = (
    r"\./"              # ./
    r"|\.\."            # ..
    r"|~"               # ~
    r"|/"               # /
    r"|[a-zA-Z]:[/\\]"  # drive letter, colon, forward or backslash
    r"|\\\\"            # windows UNC path
    r"|//"              # windows UNC path
)


def is_path(value):
    if '://' in value:
        return False
    return re.match(PATH_MATCH_REGEX, value)


def expand(path):
    if on_win and PY2:
        path = ensure_fs_path_encoding(path)
    return abspath(expanduser(expandvars(path)))


def paths_equal(path1, path2):
    """
    Examples:
        >>> paths_equal('/a/b/c', '/a/b/c/d/..')
        True

    """
    if on_win:
        return normcase(abspath(path1)) == normcase(abspath(path2))
    else:
        return abspath(path1) == abspath(path2)


@memoize
def url_to_path(url):
    """Convert a file:// URL to a path.

    Relative file URLs (i.e. `file:relative/path`) are not supported.
    """
    if is_path(url):
        return url
    if not url.startswith("file://"):  # pragma: no cover
        raise CondaError("You can only turn absolute file: urls into paths (not %s)" % url)
    _, netloc, path, _, _ = urlsplit(url)
    path = unquote(path)
    if netloc not in ('', 'localhost', '127.0.0.1', '::1'):
        if not netloc.startswith('\\\\'):
            # The only net location potentially accessible is a Windows UNC path
            netloc = '//' + netloc
    else:
        netloc = ''
        # Handle Windows drive letters if present
        if re.match('^/([a-z])[:|]', path, re.I):
            path = path[1] + ':' + path[3:]
    return netloc + path


def tokenized_startswith(test_iterable, startswith_iterable):
    return all(t == sw for t, sw in zip(test_iterable, startswith_iterable))


def get_all_directories(files):
    return sorted(set(tuple(f.split('/')[:-1]) for f in files) - {()})


def get_leaf_directories(files):
    # type: (List[str]) -> List[str]
    # give this function a list of files, and it will hand back a list of leaf directories to
    #   pass to os.makedirs()
    directories = get_all_directories(files)
    if not directories:
        return ()

    leaves = []

    def _process(x, y):
        if not tokenized_startswith(y, x):
            leaves.append(x)
        return y
    last = reduce(_process, directories)

    if not leaves:
        leaves.append(directories[-1])
    elif not tokenized_startswith(last, leaves[-1]):
        leaves.append(last)

    return tuple('/'.join(leaf) for leaf in leaves)


def explode_directories(child_directories, already_split=False):
    # get all directories including parents
    # use already_split=True for the result of get_all_directories()
    maybe_split = lambda x: x if already_split else x.split('/')
    return set(concat(accumulate(join, maybe_split(directory))
                      for directory in child_directories if directory))


def pyc_path(py_path, python_major_minor_version):
    pyver_string = python_major_minor_version.replace('.', '')
    if pyver_string.startswith('2'):
        return py_path + 'c'
    else:
        directory, py_file = split(py_path)
        basename_root, extension = splitext(py_file)
        pyc_file = "__pycache__/%s.cpython-%s%sc" % (basename_root, pyver_string, extension)
        return "%s/%s" % (directory, pyc_file) if directory else pyc_file


def missing_pyc_files(python_major_minor_version, files):
    # returns a tuple of tuples, with the inner tuple being the .py file and the missing .pyc file
    py_files = (f for f in files if f.endswith('.py'))
    pyc_matches = ((py_file, pyc_path(py_file, python_major_minor_version))
                   for py_file in py_files)
    result = tuple(match for match in pyc_matches if match[1] not in files)
    return result


def parse_entry_point_def(ep_definition):
    cmd_mod, func = ep_definition.rsplit(':', 1)
    command, module = cmd_mod.rsplit("=", 1)
    command, module, func = command.strip(), module.strip(), func.strip()
    return command, module, func


def get_python_short_path(python_version=None):
    if on_win:
        return "python.exe"
    if python_version and '.' not in python_version:
        python_version = '.'.join(python_version)
    return join("bin", "python%s" % (python_version or ''))


def get_python_site_packages_short_path(python_version):
    if python_version is None:
        return None
    elif on_win:
        return 'Lib/site-packages'
    else:
        py_ver = get_major_minor_version(python_version)
        return 'lib/python%s/site-packages' % py_ver


def get_major_minor_version(string, with_dot=True):
    # returns None if not found, otherwise two digits as a string
    # should work for
    #   - 3.5.2
    #   - 27
    #   - bin/python2.7
    #   - lib/python34/site-packages/
    # the last two are dangers because windows doesn't have version information there
    assert isinstance(string, string_types)
    digits = tuple(take(2, (c for c in string if c.isdigit())))
    if len(digits) == 2:
        return '.'.join(digits) if with_dot else ''.join(digits)
    return None


def get_bin_directory_short_path():
    return 'Scripts' if on_win else 'bin'


def win_path_ok(path):
    return path.replace('/', '\\') if on_win else path


def win_path_double_escape(path):
    return path.replace('\\', '\\\\') if on_win else path


def win_path_backout(path):
    # replace all backslashes except those escaping spaces
    # if we pass a file url, something like file://\\unc\path\on\win, make sure
    #   we clean that up too
    return re.sub(r"(\\(?! ))", r"/", path).replace(':////', '://')


def ensure_pad(name, pad="_"):
    """

    Examples:
        >>> ensure_pad('conda')
        '_conda_'
        >>> ensure_pad('_conda')
        '__conda_'
        >>> ensure_pad('')
        ''

    """
    if not name or name[0] == name[-1] == pad:
        return name
    else:
        return "%s%s%s" % (pad, name, pad)


def is_private_env_name(env_name):
    """

    Examples:
        >>> is_private_env_name("_conda")
        False
        >>> is_private_env_name("_conda_")
        True

    """
    return env_name and env_name[0] == env_name[-1] == "_"


def is_private_env_path(env_path):
    """

    Examples:
        >>> is_private_env_path('/some/path/to/envs/_conda_')
        True
        >>> is_private_env_path('/not/an/envs_dir/_conda_')
        False

    """
    if env_path is not None:
        envs_directory, env_name = split(env_path)
        if basename(envs_directory) != "envs":
            return False
        return is_private_env_name(env_name)
    return False


def right_pad_os_sep(path):
    return path if path.endswith(os.sep) else path + os.sep


def split_filename(path_or_url):
    dn, fn = split(path_or_url)
    return (dn or None, fn) if '.' in fn else (path_or_url, None)


def get_python_noarch_target_path(source_short_path, target_site_packages_short_path):
    if source_short_path.startswith('site-packages/'):
        sp_dir = target_site_packages_short_path
        return source_short_path.replace('site-packages', sp_dir, 1)
    elif source_short_path.startswith('python-scripts/'):
        bin_dir = get_bin_directory_short_path()
        return source_short_path.replace('python-scripts', bin_dir, 1)
    else:
        return source_short_path


def win_path_to_unix(path, root_prefix=""):
    # If the user wishes to drive conda from MSYS2 itself while also having
    # msys2 packages in their environment this allows the path conversion to
    # happen relative to the actual shell. The onus is on the user to set
    # CYGPATH to e.g. /usr/bin/cygpath.exe (this will be translated to e.g.
    # (C:\msys32\usr\bin\cygpath.exe by MSYS2) to ensure this one is used.
    if not path:
        return ''
    cygpath = os.environ.get('CYGPATH', 'cygpath.exe')
    try:
        path = subprocess.check_output([cygpath, '-up', path]).decode('ascii').split('\n')[0]
    except Exception as e:
        log.debug('%r' % e, exc_info=True)
        # Convert a path or ;-separated string of paths into a unix representation
        # Does not add cygdrive.  If you need that, set root_prefix to "/cygdrive"
        def _translation(found_path):  # NOQA
            found = found_path.group(1).replace("\\", "/").replace(":", "").replace("//", "/")
            return root_prefix + "/" + found
        path_re = '(?<![:/^a-zA-Z])([a-zA-Z]:[\/\\\\]+(?:[^:*?"<>|]+[\/\\\\]+)*[^:*?"<>|;\/\\\\]+?(?![a-zA-Z]:))'  # noqa
        path = re.sub(path_re, _translation, path).replace(";/", ":/")
    return path


def which(executable):
    from distutils.spawn import find_executable
    return find_executable(executable)
