# -*- coding: ascii -*-
#
# Copyright 2007 - 2013
# Andr\xe9 Malo or his licensors, as applicable
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
=================
 Shell utilities
=================

Shell utilities.
"""
from __future__ import generators

__author__ = u"Andr\xe9 Malo"
__docformat__ = "restructuredtext en"

import errno as _errno
import fnmatch as _fnmatch
import os as _os
import shutil as _shutil
import sys as _sys
import tempfile as _tempfile

cwd = _os.path.dirname(_os.path.abspath(_sys.argv[0]))

class ExitError(RuntimeError):
    """ Exit error """
    def __init__(self, code):
        RuntimeError.__init__(self, code)
        self.code = code
        self.signal = None


class SignalError(ExitError):
    """ Signal error """
    def __init__(self, code, signal):
        ExitError.__init__(self, code)
        import signal as _signal
        self.signal = signal
        for key, val in vars(_signal).iteritems():
            if key.startswith('SIG') and not key.startswith('SIG_'):
                if val == signal:
                    self.signalstr = key[3:]
                    break
        else:
            self.signalstr = '%04d' % signal


def native(path):
    """ Convert slash path to native """
    path = _os.path.sep.join(path.split('/'))
    return _os.path.normpath(_os.path.join(cwd, path))


def cp(src, dest):
    """ Copy src to dest """
    _shutil.copy2(native(src), native(dest))


def cp_r(src, dest):
    """ Copy -r src to dest """
    _shutil.copytree(native(src), native(dest))


def rm(dest):
    """ Remove a file """
    try:
        _os.unlink(native(dest))
    except OSError, e:
        if _errno.ENOENT != e.errno:
            raise

def rm_rf(dest):
    """ Remove a tree """
    dest = native(dest)
    if _os.path.exists(dest):
        for path in files(dest, '*'):
            _os.chmod(native(path), 0644)
        _shutil.rmtree(dest)


try:
    mkstemp = _tempfile.mkstemp
except AttributeError:
    # helpers stolen from 2.4 tempfile module
    try:
        import fcntl as _fcntl
    except ImportError:
        def _set_cloexec(fd):
            """ Set close-on-exec (not implemented, but not an error) """
            # pylint: disable = W0613
            pass
    else:
        def _set_cloexec(fd):
            """ Set close-on-exec """
            try:
                flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0)
            except IOError:
                pass
            else:
                # flags read successfully, modify
                flags |= _fcntl.FD_CLOEXEC
                _fcntl.fcntl(fd, _fcntl.F_SETFD, flags)

    _text_openflags = _os.O_RDWR | _os.O_CREAT | _os.O_EXCL
    _text_openflags |= getattr(_os, 'O_NOINHERIT', 0)
    _text_openflags |= getattr(_os, 'O_NOFOLLOW', 0)

    _bin_openflags = _text_openflags
    _bin_openflags |= getattr(_os, 'O_BINARY', 0)

    def mkstemp(suffix="", prefix=_tempfile.gettempprefix(), dir=None,
                text=False):
        """ Create secure temp file """
        # pylint: disable = W0622
        if dir is None:
            dir = _tempfile.gettempdir()
        if text:
            flags = _text_openflags
        else:
            flags = _bin_openflags
        count = 100
        while count > 0:
            j = _tempfile._counter.get_next() # pylint: disable = E1101, W0212
            fname = _os.path.join(dir, prefix + str(j) + suffix)
            try:
                fd = _os.open(fname, flags, 0600)
            except OSError, e:
                if e.errno == _errno.EEXIST:
                    count -= 1
                    continue
                raise
            _set_cloexec(fd)
            return fd, _os.path.abspath(fname)
        raise IOError, (_errno.EEXIST, "No usable temporary file name found")


def _pipespawn(argv, env):
    """ Pipe spawn """
    # pylint: disable = R0912
    import pickle as _pickle
    fd, name = mkstemp('.py')
    try:
        _os.write(fd, (r"""
import os
import pickle
try:
    import subprocess
except ImportError:
    subprocess = None
import sys

argv = pickle.loads(%(argv)s)
env = pickle.loads(%(env)s)
if 'X_JYTHON_WA_PATH' in env:
    env['PATH'] = env['X_JYTHON_WA_PATH']

if subprocess is None:
    pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env)
    result = os.waitpid(pid, 0)[1]
else:
    p = subprocess.Popen(argv, env=env)
    result = p.wait()
    if result < 0:
        print "\n%%d 1" %% (-result)
        sys.exit(2)

if result == 0:
    sys.exit(0)
signalled = getattr(os, 'WIFSIGNALED', None)
if signalled is not None:
    if signalled(result):
        print "\n%%d %%d" %% (os.WTERMSIG(result), result & 7)
        sys.exit(2)
print "\n%%d" %% (result & 7,)
sys.exit(3)
        """.strip() + "\n") % {
            'argv': repr(_pickle.dumps(argv)),
            'env': repr(_pickle.dumps(env)),
        })
        fd, _ = None, _os.close(fd)
        if _sys.platform == 'win32':
            argv = []
            for arg in [_sys.executable, name]:
                if ' ' in arg or arg.startswith('"'):
                    arg = '"%s"' % arg.replace('"', '\\"')
                argv.append(arg)
            argv = ' '.join(argv)
            shell = True
            close_fds = False
        else:
            argv = [_sys.executable, name]
            shell = False
            close_fds = True

        res = 0
        try:
            import subprocess
        except ImportError:
            import popen2 as _popen2
            proc = _popen2.Popen3(argv, False)
            try:
                proc.tochild.close()
                result = proc.fromchild.read()
            finally:
                res = proc.wait()
        else:
            if 'X_JYTHON_WA_PATH' in env:
                env['PATH'] = env['X_JYTHON_WA_PATH']

            proc = subprocess.Popen(argv,
                shell=shell,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                close_fds=close_fds,
                env=env,
            )
            try:
                proc.stdin.close()
                result = proc.stdout.read()
            finally:
                res = proc.wait()
        if res != 0:
            if res == 2:
                signal, code = map(int, result.splitlines()[-1].split())
                raise SignalError(code, signal)
            elif res == 3:
                code = int(result.splitlines()[-1].strip())
                raise ExitError(code)
            raise ExitError(res)

        return result
    finally:
        try:
            if fd is not None:
                _os.close(fd)
        finally:
            _os.unlink(name)


def _filepipespawn(infile, outfile, argv, env):
    """ File Pipe spawn """
    try:
        import subprocess
    except ImportError:
        subprocess = None
    import pickle as _pickle
    fd, name = mkstemp('.py')
    try:
        _os.write(fd, ("""
import os
import pickle
import sys

infile = pickle.loads(%(infile)s)
outfile = pickle.loads(%(outfile)s)
argv = pickle.loads(%(argv)s)
env = pickle.loads(%(env)s)

if infile is not None:
    infile = open(infile, 'rb')
    os.dup2(infile.fileno(), 0)
    infile.close()
if outfile is not None:
    outfile = open(outfile, 'wb')
    os.dup2(outfile.fileno(), 1)
    outfile.close()

pid = os.spawnve(os.P_NOWAIT, argv[0], argv, env)
result = os.waitpid(pid, 0)[1]
sys.exit(result & 7)
        """.strip() + "\n") % {
            'infile': repr(_pickle.dumps(_os.path.abspath(infile))),
            'outfile': repr(_pickle.dumps(_os.path.abspath(outfile))),
            'argv': repr(_pickle.dumps(argv)),
            'env': repr(_pickle.dumps(env)),
        })
        fd, _ = None, _os.close(fd)
        if _sys.platform == 'win32':
            argv = []
            for arg in [_sys.executable, name]:
                if ' ' in arg or arg.startswith('"'):
                    arg = '"%s"' % arg.replace('"', '\\"')
                argv.append(arg)
            argv = ' '.join(argv)
            close_fds = False
            shell = True
        else:
            argv = [_sys.executable, name]
            close_fds = True
            shell = False

        if subprocess is None:
            pid = _os.spawnve(_os.P_NOWAIT, argv[0], argv, env)
            return _os.waitpid(pid, 0)[1]
        else:
            p = subprocess.Popen(
                argv, env=env, shell=shell, close_fds=close_fds
            )
            return p.wait()
    finally:
        try:
            if fd is not None:
                _os.close(fd)
        finally:
            _os.unlink(name)


def spawn(*argv, **kwargs):
    """ Spawn a process """
    try:
        import subprocess
    except ImportError:
        subprocess = None

    if _sys.platform == 'win32':
        newargv = []
        for arg in argv:
            if not arg or ' ' in arg or arg.startswith('"'):
                arg = '"%s"' % arg.replace('"', '\\"')
            newargv.append(arg)
        argv = newargv
        close_fds = False
        shell = True
    else:
        close_fds = True
        shell = False

    env = kwargs.get('env')
    if env is None:
        env = dict(_os.environ)
    if 'X_JYTHON_WA_PATH' in env:
        env['PATH'] = env['X_JYTHON_WA_PATH']

    echo = kwargs.get('echo')
    if echo:
        print ' '.join(argv)
    filepipe = kwargs.get('filepipe')
    if filepipe:
        return _filepipespawn(
            kwargs.get('stdin'), kwargs.get('stdout'), argv, env
        )
    pipe = kwargs.get('stdout')
    if pipe:
        return _pipespawn(argv, env)

    if subprocess is None:
        pid = _os.spawnve(_os.P_NOWAIT, argv[0], argv, env)
        return _os.waitpid(pid, 0)[1]
    else:
        p = subprocess.Popen(argv, env=env, shell=shell, close_fds=close_fds)
        return p.wait()


try:
    walk = _os.walk
except AttributeError:
    # copy from python 2.4 sources (modulo docs and comments)
    def walk(top, topdown=True, onerror=None):
        """ directory tree walker """
        # pylint: disable = C0103
        join, isdir, islink = _os.path.join, _os.path.isdir, _os.path.islink
        listdir, error = _os.listdir, _os.error

        try:
            names = listdir(top)
        except error, err:
            if onerror is not None:
                onerror(err)
            return

        dirs, nondirs = [], []
        for name in names:
            if isdir(join(top, name)):
                dirs.append(name)
            else:
                nondirs.append(name)

        if topdown:
            yield top, dirs, nondirs
        for name in dirs:
            path = join(top, name)
            if not islink(path):
                for x in walk(path, topdown, onerror):
                    yield x
        if not topdown:
            yield top, dirs, nondirs


def files(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')):
    """ Determine a filelist """
    for dirpath, dirnames, filenames in walk(native(base)):
        for item in prune:
            if item in dirnames:
                dirnames.remove(item)

        filenames.sort()
        for name in _fnmatch.filter(filenames, wildcard):
            dest = _os.path.join(dirpath, name)
            if dest.startswith(cwd):
                dest = dest.replace(cwd, '', 1)
            aslist = []
            head, tail = _os.path.split(dest)
            while tail:
                aslist.append(tail)
                head, tail = _os.path.split(head)
            aslist.reverse()
            dest = '/'.join(aslist)
            yield dest

        if not recursive:
            break
        dirnames.sort()


def dirs(base, wildcard='[!.]*', recursive=1, prune=('.git', '.svn', 'CVS')):
    """ Determine a filelist """
    for dirpath, dirnames, filenames in walk(native(base)):
        for item in prune:
            if item in dirnames:
                dirnames.remove(item)

        dirnames.sort()
        for name in _fnmatch.filter(dirnames, wildcard):
            dest = _os.path.join(dirpath, name)
            if dest.startswith(cwd):
                dest = dest.replace(cwd, '', 1)
            aslist = []
            head, tail = _os.path.split(dest)
            while tail:
                aslist.append(tail)
                head, tail = _os.path.split(head)
            aslist.reverse()
            dest = '/'.join(aslist)
            yield dest

        if not recursive:
            break


def frompath(executable):
    """ Find executable in PATH """
    # Based on distutils.spawn.find_executable.
    path = _os.environ.get('PATH', '')
    paths = [
        _os.path.expanduser(item)
        for item in path.split(_os.pathsep)
    ]
    ext = _os.path.splitext(executable)[1]
    exts = ['']
    if _sys.platform == 'win32' or _os.name == 'os2':
        eext = ['.exe', '.bat', '.py']
        if ext not in eext:
            exts.extend(eext)

    for ext in exts:
        if not _os.path.isfile(executable + ext):
            for path in paths:
                fname = _os.path.join(path, executable + ext)
                if _os.path.isfile(fname):
                    # the file exists, we have a shot at spawn working
                    return fname
        else:
            return executable + ext

    return None
