'''fileops.py

Performs file operations such as pack/unpack,
ensuring directories exist,... imports snakeoils osutils
functions for use throughout catalyst.
'''

import glob
import os
import shutil
from stat import ST_UID, ST_GID, ST_MODE

from snakeoil.osutils import ensure_dirs as snakeoil_ensure_dirs

from catalyst import log
from catalyst.support import CatalystError


def ensure_dirs(path, gid=-1, uid=-1, mode=0o755, minimal=True,
                failback=None, fatal=False):
    '''Wrapper to snakeoil.osutil's ensure_dirs()
    This additionally allows for failures to run
    cleanup or other code and/or raise fatal errors.

    :param path: directory to ensure exists on disk
    :param gid: a valid GID to set any created directories to
    :param uid: a valid UID to set any created directories to
    :param mode: permissions to set any created directories to
    :param minimal: boolean controlling whether or not the specified mode
            must be enforced, or is the minimal permissions necessary.  For example,
            if mode=0o755, minimal=True, and a directory exists with mode 0707,
            this will restore the missing group perms resulting in 757.
    :param failback: function to run in the event of a failed attemp
            to create the directory.
    :return: True if the directory could be created/ensured to have those
            permissions, False if not.
    '''
    succeeded = snakeoil_ensure_dirs(
        path, gid=gid, uid=uid, mode=mode, minimal=minimal)
    if not succeeded:
        if failback:
            failback()
        if fatal:
            raise CatalystError(
                "Failed to create directory: %s" % path, print_traceback=True)
    return succeeded


def clear_dir(target, mode=0o755, remove=False):
    '''Universal directory clearing function

    @target: string, path to be cleared or removed
    @mode: integer, desired mode to set the directory to
    @remove: boolean, passed through to clear_dir()
    @return boolean
    '''
    log.debug('start: %s', target)
    if not target:
        log.debug('no target... returning')
        return False

    mystat = None
    if os.path.isdir(target) and not os.path.islink(target):
        log.notice('Emptying directory: %s', target)
        # stat the dir, delete the dir, recreate the dir and set
        # the proper perms and ownership
        try:
            log.debug('os.stat()')
            mystat = os.stat(target)
            log.debug('shutil.rmtree()')
            shutil.rmtree(target)
        except Exception:
            log.error('clear_dir failed', exc_info=True)
            return False
    elif os.path.exists(target):
        log.debug("Clearing (unlinking) non-directory: %s", target)
        os.unlink(target)
    else:
        log.debug("Conditions not met to clear: %s", target)
        log.debug("                      isdir: %s", os.path.isdir(target))
        log.debug("                     islink: %s", os.path.islink(target))
        log.debug("                     exists: %s", os.path.exists(target))

    if not remove:
        log.debug('ensure_dirs()')
        ensure_dirs(target, mode=mode)
        if mystat:
            os.chown(target, mystat[ST_UID], mystat[ST_GID])
            os.chmod(target, mystat[ST_MODE])

    log.debug('DONE, returning True')
    return True


def clear_path(target_path):
    """Nuke |target_path| regardless of it being a dir, file or glob."""
    targets = glob.iglob(target_path, recursive=True)
    for path in targets:
        clear_dir(path, remove=True)


def move_path(src, dest):
    '''Move a source target to a new destination

    :param src: source path to move
    :param dest: destination path to move it to
    :returns: boolean
    '''
    log.debug('Start move_path(%s, %s)', src, dest)
    if os.path.isdir(src) and not os.path.islink(src):
        if os.path.exists(dest):
            log.warning('Removing existing target destination: %s', dest)
            if not clear_dir(dest, remove=True):
                return False
        log.debug('Moving source...')
        try:
            shutil.move(src, dest)
        except Exception:
            log.error('move_path failed', exc_info=True)
            return False
        return True
    return False
