import os
import platform

from injector import inject, singleton

from ..config import Config, Setting
from ..exceptions import LowSpaceError
from .globalinfo import GlobalInfo
from ..logger import getLogger

logger = getLogger(__name__)

SIZE_SI = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]


@singleton
class Estimator():
    @inject
    def __init__(self, config: Config, global_info: GlobalInfo):
        super().__init__()
        self.config = config
        self._blocksUsed = 0
        self._blocksTotal = 1
        self._blockSize = 0
        self._global_info = global_info

    def refresh(self):
        if platform.system() == "Windows":
            # Unsupported on windows
            return self
        # This roughly matches the results you get by running "df",
        # except its a bit more conservative, which isn't necessarily
        # correct, but we're aiming for ballpark numbers here so it
        # should be ok.
        stats = os.statvfs(self.config.get(Setting.BACKUP_DIRECTORY_PATH))
        total = stats.f_blocks
        available = stats.f_bavail
        availableToRoot = stats.f_bfree
        self._blocksUsed = total - availableToRoot
        self._blocksTotal = self._blocksUsed + available
        self._blockSize = stats.f_frsize
        return self

    def checkSpace(self, backups):
        if platform.system() == "Windows":
            # Unsupported on windows
            return
        if not self.config.get(Setting.WARN_FOR_LOW_SPACE):
            # Don't check, just go
            return
        try:
            self._checkSpace(backups)
        except Exception as e:
            if isinstance(e, LowSpaceError):
                raise e
            # Just log the error and continue otherwise
            logger.error(
                "Encountered an error while trying to check disk space remaining: " + str(e))

    def _checkSpace(self, backups):
        # get the most recent backup size
        space_needed = self.config.get(Setting.LOW_SPACE_THRESHOLD)
        backups.sort(key=lambda s: s.date(), reverse=True)
        for backup in backups:
            latest_size = backup.sizeInt()
            if latest_size > 1:
                # Bump the size a little to estimate organic growth.
                space_needed = latest_size * 1.1
                break

        if space_needed > self.getBytesFree() and not self._global_info.isSkipSpaceCheckOnce():
            raise LowSpaceError("{0}%".format(
                int(self.getUsagePercent())), Estimator.asSizeString(self.getBytesFree()))

    def getUsagePercent(self):
        if self.getBlocksTotal() == 0:
            return 0
        return 100.0 * float(self.getBlocksUsed()) / float(self.getBlocksTotal())

    def getBlocksUsed(self):
        return self._blocksUsed

    def getBlocksTotal(self):
        return self._blocksTotal

    def getBlocksFree(self):
        return self.getBlocksTotal() - self.getBlocksUsed()

    def getBytesFree(self):
        return self._blockSize * self.getBlocksFree()

    def getBytesUsed(self):
        return self._blockSize * self.getBlocksUsed()

    def getBytesTotal(self):
        return self._blockSize * self.getBlocksTotal()

    @classmethod
    def asSizeString(cls, size):
        current = float(size)
        for id in SIZE_SI:
            if current < 1024:
                return "{0} {1}".format(round(current, 1), id)
            current /= 1024
        return "Beyond mortal comprehension"
