# python3X
"""
    Convenient methods for managing files and directories
    @author: olivier.massot
"""
import os
import re
import shutil
import subprocess
import sys
import tempfile


__VERSION__ = "0.2"


def fdir(path):
    """return the parent directory of the file or directory
    (no file existence control)"""
    if path[-1] == os.path.sep:
        path = os.path.dirname(path)
    return os.path.normpath(os.path.dirname(path))

def fname(path, with_ext=True):
    """return the name of the file
    with_ext: return the name with its extension
    (no file existence control)"""
    if not with_ext:
        path, _ = os.path.splitext(path)
    if path[-1] == os.path.sep:
        path = os.path.dirname(path)
    return os.path.basename(path)

def fext(path):
    """return the extension of the file
    (no file existence control)"""
    return os.path.splitext(path)[1]

def fabs(path):
    """ return the absolute expanded normalized path """
    return os.path.abspath(os.path.expandvars(os.path.expanduser(path)))

def fexists(path):
    """test the existence of a file or directory"""
    return os.path.exists(os.path.normpath(path))

def fisurl(instr):
    """return True if the 'instr' is a web url"""
    return re.match(r"((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)", \
                    instr)

def fstart(path):
    """run/open the required file, directory, webpage... with the default software"""
    if not fisurl(path):
        path = os.path.normpath(path)
    if sys.platform == "win32":
        os.startfile(path)
    else:
        opener = "open" if sys.platform == "darwin" else "xdg-open"
        subprocess.call([opener, path])

def flist(target_dir, files=True, dirs=False, recursive=True):
    """list the subdirectories and/or the files 'relative paths inside the directory
    """
    if not os.path.isdir(target_dir):
        raise NotADirectoryError("'{var}' is not an existing directory".format(var=target_dir))
    result = []
    for _root, _dirs, _files in os.walk(target_dir):
        rel_dir = os.path.relpath(_root, target_dir)
        if dirs:
            result += map(lambda p: (os.path.join(rel_dir, p) if rel_dir != "." else p), _dirs)
        if files:
            result += map(lambda p: (os.path.join(rel_dir, p) if rel_dir != "." else p), _files)
        if not recursive:
            break
    return result

def ftree(dirpath):
    """ return a set (unordered) of the paths of the sub-directories
    """
    result = set()
    for sdpath in (os.path.join(dirpath, dirname) for dirname \
                   in os.listdir(dirpath)):
        if os.path.isdir(sdpath):
            sdpath = os.path.normpath(sdpath)
            result |= ({sdpath} | ftree(sdpath))
    return result

def fcopy(source, targetdir, rename_as="", erase=False):
    """ copy the file to the target directory
    > return the path of the newly created file
    """
    source = fabs(source)
    targetdir = fabs(targetdir)

    if not os.path.isdir(targetdir):
        raise NotADirectoryError("'targetdir' has to be a directory (given: {})".format(targetdir))

    target_name, target_ext = os.path.splitext(fname(source)) if not rename_as else os.path.splitext(rename_as)
    targetpath = os.path.join(targetdir, '%s%s' % (target_name, target_ext))

    if not erase and fexists(targetpath):
        targetpath = next((os.path.join(targetdir, '%s_%d%s' % (target_name, counter, target_ext)) for counter in range(2, 100000000) \
                           if not fexists(os.path.join(targetdir, '%s_%d%s' % (target_name, counter, target_ext)))))


    shutil.copyfile(source, targetpath)
    return targetpath

def faccess(path, mode="r"):
    """ return True if user has an access to the directory or file in parameter
    """
    path = os.path.normpath(path)

    if os.path.isfile(path):
        try:
            with open(path, mode) as _:
                pass
            return True
        except PermissionError:
            return False
        except FileNotFoundError:
            # could be a directory
            pass
        except OSError:
            pass

    if os.path.isdir(path):
        # try to explore as a directory
        if mode[0] == "r":
            try:
                next(os.walk(path))
            except StopIteration:
                return False
            return True

        else:
            try:
                with tempfile.TemporaryFile(mode=mode, dir=path) as _:
                    pass
                return True
            except PermissionError:
                return False

    raise FileNotFoundError("'{path}' does not exist or is unaccessible".format(path=path))

def fjoin(*args):
    """recursively normalize and join the paths in args"""
    return os.path.join(*[os.path.normpath(p) for p in args])

def fmkdir(dirpath):
    """ create the directory if it do not exist """
    if not os.path.exists(dirpath):
        os.mkdir(dirpath)

def fmktree(dirpath):
    """ create the directory recursively """
    if not os.path.exists(os.path.dirname(dirpath)):
        fmktree(os.path.dirname(dirpath))
    fmkdir(dirpath)

def frmtree(dirpath):
    """recursively delete the directory, even it is not empty"""
    try:
        shutil.rmtree(dirpath)
    except FileNotFoundError:
        pass

def fhumansize(size):
    """return a human readable size of file"""
    suffixes = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB']
    suffix_index = 0
    while size >= 1024:
        suffix_index += 1  # increment the index of the suffix
        size /= 1024.0  # apply the division
    return "{0:03.2f} {1}".format(size, suffixes[suffix_index]) if suffix_index > 0 \
           else "{0} {1}".format(size, suffixes[suffix_index])

def fsub(dir1, dir2):
    """ return True if dir1 a sub-directory of dir2 """
    dir1, dir2 = map(fabs, [dir1, dir2])
    return os.path.commonprefix([dir1, dir2]) == dir2
