import os
import random
import string
import datetime
import multiprocessing
import hashlib
import math
import re
from typing import Generator


def get_hash(content: bytes, length: int=32) -> str:
    return hashlib.md5(content).hexdigest()[:length]


def get_random_password(length: int=32):
    return ''.join(random.choice(string.ascii_letters) for m in range(length))


def version_to_int(version: str) -> int:
    ver = re.findall(r'\d+', version)

    if 'snapshot' in version:
        pass
    elif 'alpha' in version:
        ver[-1] = str(int(ver[-1]) + 1000)
    elif 'beta' in version:
        ver[-1] = str(int(ver[-1]) + 2000)
    elif 'rc' in version:
        ver[-1] = str(int(ver[-1]) + 3000)
    else:
        ver[-1] = str(int(ver[-1]) + 4000)

    return int(''.join([x.zfill(4) for x in ver]))


def int_to_version(number: int) -> str:
    version_list = list(map(''.join, zip(*[iter(str(number).zfill(16))] * 4)))
    major, minor, patch, build = [int(i) for i in version_list]

    addition_to_state = {
        1000: 'alpha',
        2000: 'beta',
        3000: 'rc',
        4000: '',
    }

    for addition, state in addition_to_state.items():
        if build % addition < 1000 and int(build / addition) == 1:
            build = '{}{}'.format(build - addition, state)
            break

    return '{}.{}.{}.{}'.format(
        major,
        minor,
        patch,
        build,
    )


def get_mount_info(mount_path: str) -> dict:
    """
    Gets mount info
    @param mount_path:
    @return:
    """
    ret = {}
    try:
        statvfs = os.statvfs(mount_path)
        ret['size'] = statvfs.f_frsize * statvfs.f_blocks
        ret['free_reserved'] = statvfs.f_frsize * statvfs.f_bfree
        ret['free'] = statvfs.f_frsize * statvfs.f_bavail
    except Exception:
        ret['size'] = 0
        ret['free_reserved'] = 0
        ret['free'] = 0

    return ret


def directory_size(source: str) -> int:
    """
    Returns size of directory
    @param source:  path to directory
    @return:
    """
    total_size = os.path.getsize(source)
    for item in os.listdir(source):
        itempath = os.path.join(source, item)
        if os.path.isfile(itempath):
            total_size += os.path.getsize(itempath)
        elif os.path.isdir(itempath):
            total_size += directory_size(itempath)
    return total_size


def get_uptime() -> str:
    with open('/proc/uptime', 'r') as f:
        uptime_seconds = float(f.readline().split()[0])
        return str(datetime.timedelta(seconds=uptime_seconds))


def step_range(start: int, stop: int, step: int) -> Generator[int, None, None]:
    r = start
    while r < stop:
        yield r
        r += step


def get_number_of_cores() -> int:
    return multiprocessing.cpu_count()


def chunks(l: list, n: int) -> list:
    """Yield successive n-sized chunks from l."""
    ret = []
    for i in range(0, len(l), n):
        ret.append(l[i:i + n])
    return ret


class ListPagination(object):

    def __init__(self, raw_items: list, page: int, per_page: int):
        self.page = page
        self.per_page = per_page
        self.total_count = len(raw_items)
        self.items = chunks(raw_items, per_page)[page-1]

    @property
    def pages(self) -> int:
        return int(math.ceil(self.total_count / float(self.per_page)))

    @property
    def has_prev(self) -> bool:
        return self.page > 1

    @property
    def has_next(self) -> bool:
        return self.page < self.pages

    def iter_pages(self, left_edge: int=2, left_current: int=2, right_current: int=5, right_edge: int=2) -> Generator[int, None, None]:
        last = 0
        for num in range(1, self.pages + 1):
            if num <= left_edge or \
               (num > self.page - left_current - 1 and \
                num < self.page + right_current) or \
               num > self.pages - right_edge:
                if last + 1 != num:
                    yield None
                yield num
                last = num