import os.path
import re
import shlex
import subprocess
import oe.path
import bb


def run(d, cmd, *args):
    topdir = d.getVar('TOPDIR')
    toolchain_path = d.getVar('EXTERNAL_TOOLCHAIN')
    if toolchain_path:
        target_prefix = d.getVar('EXTERNAL_TARGET_SYS') + '-'
        if not cmd.startswith(target_prefix):
            cmd = target_prefix + cmd

        toolchain_bin = d.getVar('EXTERNAL_TOOLCHAIN_BIN')
        path = os.path.join(toolchain_bin, cmd)
        args = shlex.split(path) + list(args)

        bb.debug(1, 'oe.external.run({})'.format(repr(args)))
        try:
            output, _ = bb.process.run(args, cwd=topdir, stderr=subprocess.STDOUT)
        except bb.process.CmdError as exc:
            bb.debug(1, 'oe.external.run: {} failed: {}'.format(subprocess.list2cmdline(args), exc))
        else:
            return output

    return 'UNKNOWN'


def parse_mirrors(mirrors_string):
    mirrors, invalid = [], []
    for entry in mirrors_string.replace('\\n', '\n').split('\n'):
        entry = entry.strip()
        if not entry:
            continue
        try:
            pathname, subst = entry.strip().split('|', 1)
        except ValueError:
            invalid.append(entry)
        mirrors.append(('^' + re.escape(pathname), subst))
    return mirrors, invalid


def get_file_search_metadata(d):
    '''Given the metadata, return the mirrors and sysroots to operate against.'''

    premirrors, invalid = parse_mirrors(d.getVar('FILES_PREMIRRORS'))
    for invalid_entry in invalid:
        bb.warn('Invalid FILES_MIRRORS entry: {0}'.format(invalid_entry))

    mirrors, invalid = parse_mirrors(d.getVar('FILES_MIRRORS'))
    for invalid_entry in invalid:
        bb.warn('Invalid FILES_MIRRORS entry: {0}'.format(invalid_entry))

    source_paths = [os.path.realpath(p)
                    for p in d.getVar('EXTERNAL_INSTALL_SOURCE_PATHS').split()]

    return source_paths, mirrors, premirrors


def gather_pkg_files(d):
    '''Given the metadata, return all the files we want to copy to ${D} for
    this recipe.'''
    import itertools
    files = []
    for pkg in d.getVar('PACKAGES').split():
        files = itertools.chain(files, (d.getVar('EXTERNAL_FILES_{}'.format(pkg)) or d.getVar('FILES:{}'.format(pkg)) or '').split())
    files = itertools.chain(files, d.getVar('EXTERNAL_EXTRA_FILES').split())
    return files


def copy_from_sysroots(pathnames, sysroots, mirrors, premirrors, installdest):
    '''Copy the specified files from the specified sysroots, also checking the
    specified mirror patterns as alternate paths, to the specified destination.'''
    expanded_pathnames = expand_paths(pathnames, mirrors, premirrors)
    searched_paths = search_sysroots(expanded_pathnames, sysroots)
    for path, files in searched_paths:
        if not files:
            bb.debug(1, 'oe.external: failed to find `{}`'.format(path))
        else:
            destdir = oe.path.join(installdest, os.path.dirname(path))
            bb.utils.mkdirhier(destdir)
            subprocess.check_call(['cp', '-PR', '--preserve=mode,timestamps', '--no-preserve=ownership'] + list(files) + [destdir + '/'])
            bb.note('Copied `{}`  to `{}/`'.format(', '.join(files), destdir))

def expand_paths(pathnames, mirrors, premirrors):
    '''Apply search/replace to paths to get alternate search paths.

    Returns a generator with tuples of (pathname, expanded_paths).'''
    import re
    for pathname in pathnames:
        expanded_paths = []

        for search, replace in premirrors:
            try:
                new_pathname = re.sub(search, replace, pathname, count=1)
            except re.error as exc:
                bb.warn("Invalid pattern for `%s`" % search)
                continue
            if new_pathname != pathname:
                expanded_paths.append(new_pathname)

        expanded_paths.append(pathname)

        for search, replace in mirrors:
            try:
                new_pathname = re.sub(search, replace, pathname, count=1)
            except re.error as exc:
                bb.warn("Invalid pattern for `%s`" % search)
                continue
            if new_pathname != pathname:
                expanded_paths.append(new_pathname)

        yield pathname, expanded_paths

def search_sysroots(path_entries, sysroots):
    '''Search the supplied sysroots for the supplied paths, checking supplied
    alternate paths. Expects entries in the format (pathname, all_paths).

    Returns a generator with tuples of (pathname, found_paths).'''
    import glob
    import itertools
    for path, pathnames in path_entries:
        for sysroot, pathname in ((s, p) for s in sysroots
                                         for p in pathnames):
            check_path = sysroot + os.sep + pathname
            found_paths = glob.glob(check_path)
            if found_paths:
                yield path, found_paths
                break
        else:
            yield path, None


def find_sysroot_files(paths, d):
    sysroots, mirrors, premirrors = get_file_search_metadata(d)
    expanded = expand_paths(paths, mirrors, premirrors)
    search_results = list(search_sysroots(expanded, sysroots))
    return [v for k, v in search_results]
