import tempfile
from json import loads as json_loads
from os import name as os_name, getpid, makedirs, walk
from pathlib import Path
from shutil import move
from shutil import rmtree

__dir_name__ = '.PyMangaDownloader'


def mark_as_hidden(_path: str):
    try:
        from ctypes import windll
        windll.kernel32.SetFileAttributesW(_path, 2)
    except Exception:
        pass


def get_temp_path(*args) -> str:
    temp = 'temp_%s' % getpid()
    return path_join(tempfile.gettempdir(), __dir_name__, temp, *args)


def root_path() -> str:
    #      fs.py/manga_py/../
    file = Path(__file__).resolve()
    return str(file.parent.parent)


def get_util_home_path() -> str:
    if os_name == 'nt':
        home = path_join(str(Path.home()), 'AppData', 'Roaming', __dir_name__)
    else:
        home = path_join(str(Path.home()), __dir_name__)
    make_dirs(home)
    return str(home)


def make_dirs(directory):
    is_dir(directory) or makedirs(directory)


def remove_file_query_params(name, save_path: bool = True) -> str:
    if name is None:
        raise AttributeError
    file_path = dirname(name)
    name = basename(name)
    position = name.find('?')
    if position == 0:
        name = 'image.png'  # fake image name
    elif position > 0:
        name = name[:position]
    return str(path_join(file_path, name) if save_path else name)


def is_file(_path) -> bool:
    return Path(_path).is_file()


def is_dir(_path) -> bool:
    return Path(_path).is_dir()


def basename(_path) -> str:
    return str(Path(_path).name)


def dirname(_path) -> str:
    return str(Path(_path).parent)


def path_join(_path, *args) -> str:
    return str(Path(_path).joinpath(*args))


def unlink(_path, allow_not_empty=False):
    if is_dir(_path):
        if allow_not_empty:
            rmtree(_path)
        else:
            Path(_path).rmdir()
    elif is_file(_path):
        Path(_path).unlink()


def os_stat(_path):
    if is_file(_path):
        return Path(_path).stat()
    return None


def file_size(_path):
    """
    :param _path:
    :return:
    :rtype: int
    """
    data = os_stat(_path)
    if data:
        return data.st_size
    return None


def rename(_from, _to):
    if is_file(_from) or is_dir(_from):
        is_dir(dirname(_to)) or makedirs(dirname(_to))
        move(_from, _to)


def storage(_path) -> str:
    _path = get_temp_path('storage', _path)
    make_dirs(dirname(_path))
    return str(_path)


def listing(_path) -> dict:
    """
    :param _path:
    :return: {'directories': [], 'files': []}
    """
    _dirname, _dirnames, _filenames = walk(_path)
    return {'directories': _dirnames, 'files': _filenames}


def __get_info(_path):
    try:
        with open(path_join(_path, 'info.json'), 'r') as r:
            return json_loads(r.read())
    except FileNotFoundError:
        return None


def get_info(_path) -> dict:
    """
    listing subdirectories and reading info.json files
    :param _path: [{..}, {..}, {..}]
    :return:
    """
    result = {}
    for d in listing(_path)['directories']:
        directory = path_join(_path, d)
        info = __get_info(directory)
        if info is not None:
            result[directory] = info
    return result


def __dirname(_path) -> str:
    if not is_dir(_path):
        _path = __dirname(dirname(_path))
    return str(_path)


def _disk_stat_posix(_path) -> dict:
    import os
    st = os.statvfs(_path)
    free = st.f_bavail * st.f_frsize
    total = st.f_blocks * st.f_frsize
    used = (st.f_blocks - st.f_bfree) * st.f_frsize
    return {'total': total, 'used': used, 'free': free}


def _disc_stat_win(_path) -> dict:
    import ctypes
    _, total, free = ctypes.c_ulonglong(), ctypes.c_ulonglong(), ctypes.c_ulonglong()
    fun = ctypes.windll.kernel32.GetDiskFreeSpaceExA
    ret = fun(_path, ctypes.byref(_), ctypes.byref(total), ctypes.byref(free))
    if ret == 0:
        fun = ctypes.windll.kernel32.GetDiskFreeSpaceExW
        ret = fun(_path, ctypes.byref(_), ctypes.byref(total), ctypes.byref(free))
        if ret == 0:
            raise ctypes.WinError()
    used = total.value - free.value
    return {'total': total.value, 'used': used, 'free': free.value}


def get_disk_stat(_path) -> dict:
    import os
    _path = __dirname(_path)

    if hasattr(os, 'statvfs'):  # POSIX
        return _disk_stat_posix(_path)
    elif os.name == 'nt':  # Windows
        return _disc_stat_win(_path)
    else:
        raise NotImplementedError('Platform not supported')


def check_free_space(_path: str, min_size: int = 100, percent: bool = False) -> bool:
    """
    min_size = 10  # percent = True
    min_size = 10  # percent = False (default)

    :param _path:
    :param min_size:
    :param percent:
    :return:
    """
    _stat = get_disk_stat(_path)
    if percent:
        _free = _stat['free'] / _stat['total']
        if (_free * 100) < min_size:
            return False
        return True
    else:
        _free = _stat['free'] / (2 << 19)  # 1Mb
        if _free < min_size:
            return False
        return True
