import contextlib
import logging
import os

LOG = logging.getLogger()


def mkdirs(*dirnames):
    for dirname in dirnames:
        if not dirname:
            continue # '' => curdir
        if not os.path.isdir(dirname):
            os.makedirs(dirname)
            if len(dirnames) == 1:
                LOG.debug('mkdir -p "{}"'.format(dirnames[0]))


def syscall(call, nocheck=False):
    """Raise Exception in error, unless nocheck==True
    """
    LOG.info('$(%s)' % repr(call))
    rc = os.system(call)
    msg = 'Call %r returned %d.' % (call, rc)
    if rc:
        LOG.warning(msg)
        if not nocheck:
            raise Exception(msg)
    else:
        LOG.debug(msg)
    return rc


def capture(cmd, nocheck=False):
    """Capture output, maybe checking return-code.
    Return stdout, fully captured.
    Wait for subproc to finish.
    Warn if empty.
    Raise on non-zero exit-code, unless nocheck.
    """
    import subprocess
    LOG.info('$ {} >'.format(cmd))
    proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='ascii')
    stdout, stderr = proc.communicate()
    rc = proc.returncode
    if rc:
        msg = '{} <- {!r}:\n{}'.format(rc, cmd, stdout)
        if nocheck:
            LOG.debug(msg)
        else:
            raise Exception(msg)
    assert stderr is None, '{!r} != None'.format(stderr)
    output = stdout
    if not output:
        msg = '{!r} failed to produce any output.'.format(cmd)
        LOG.warning(msg)
    return output


def symlink(src, name, force=True):
    if os.path.lexists(name):
        os.unlink(name)
    os.symlink(src, name)


def fix_relative_symlinks(currdir, origdir, recursive=True, relparent='..'):
    """
    Fix relative symlinks after cp/rsync, assuming they had
    been defined relative to 'origdir'.
    If 'recursive', then perform this in all (non-symlinked) sub-dirs also.
    Skip relative links that point upward shallower than relparent, and warn.
    (Always skip absolute symlinks; we assume those already point to persistent space.)
    """
    if recursive:
        for dn in os.listdir(currdir):
            if not os.path.islink(dn) and os.path.isdir(dn):
                fix_relative_symlinks(os.path.join(currdir, dn), os.path.join(origdir, dn), recursive,
                        os.path.join('..', relparent))
    for fn in os.listdir(currdir):
        fn = os.path.join(currdir, fn)
        if not os.path.islink(fn):
            continue
        oldlink = os.readlink(fn)
        if os.path.isabs(oldlink):
            continue
        if not os.path.normpath(oldlink).startswith(relparent):
            msg = 'Symlink {}->{} seems to point within the origdir tree. This is unexpected. relparent={}'.format(
                fn, oldlink, relparent)
            raise Exception(msg)
            #LOG.warning(msg)
            #continue
        newlink = os.path.relpath(os.path.join(origdir, oldlink), currdir)
        LOG.debug('Fix symlink to {!r} from {!r}'.format(newlink, oldlink))
        symlink(newlink, fn)


def rm(*f):
    syscall('rm -f {}'.format(' '.join(f)))


def touch(*paths):
    msg = 'touch {!r}'.format(paths)
    LOG.debug(msg)
    for path in paths:
        if os.path.exists(path):
            os.utime(path, None)
        else:
            open(path, 'a').close()


def filesize(fn):
    """In bytes.
    Raise if fn does not exist.
    """
    return os.stat(fn).st_size


def exists_and_not_empty(fn):
    if not os.path.exists(fn):
        return False
    if 0 == filesize(fn):
        LOG.debug('File {} is empty.'.format(fn))
        return False
    return True


@contextlib.contextmanager
def cd(newdir):
    # https://stackoverflow.com/a/24176022
    prevdir = os.getcwd()
    LOG.info('CD: %r <- %r' % (newdir, prevdir))
    os.chdir(os.path.expanduser(newdir))
    try:
        yield
    finally:
        LOG.info('CD: %r -> %r' % (newdir, prevdir))
        os.chdir(prevdir)
