"""WizardKit: Standard Functions"""
# vim: sts=2 sw=2 ts=2

import logging
import platform
import re
import time


# STATIC VARIABLES
LOG = logging.getLogger(__name__)
PLATFORM = platform.system()
REGEX_SIZE_STRING = re.compile(
  r'(?P<size>\-?\d+\.?\d*)\s*(?P<units>[PTGMKB])(?P<binary>I?)B?'
  )


# Exception Classes
class GenericAbort(Exception):
  """Exception used for aborts selected by the user at runtime."""

class GenericError(Exception):
  """Exception used when the built-in exceptions don't fit."""

class GenericWarning(Exception):
  """Exception used to highlight non-critical events.

  NOTE: Avoiding built-in warning exceptions in case the
        warnings filter has been changed from the default.
  """


# Functions
def bytes_to_string(
    size: float | int,
    decimals: int = 0,
    use_binary: bool = True) -> str:
  """Convert size into a human-readable format, returns str.

  [Doctest]
  >>> bytes_to_string(10)
  '10   B'
  >>> bytes_to_string(10_000_000)
  '10 MiB'
  >>> bytes_to_string(10_000_000, decimals=2)
  '9.54 MiB'
  >>> bytes_to_string(10_000_000, decimals=2, use_binary=False)
  '10.00 MB'
  >>> bytes_to_string(-10_000_000, decimals=4)
  '-9.5367 MiB'
  """
  LOG.debug(
    'size: %s, decimals: %s, use_binary: %s',
    size,
    decimals,
    use_binary,
    )
  scale = 1024 if use_binary else 1000
  size = float(size)
  suffix = '  ' if use_binary else ' '
  units = list('KMGTPEZY')

  # Convert to sensible units
  while units:
    if abs(size) < scale:
      break
    size /= scale
    suffix = units.pop(0)
  size_str = (
    f'{size:0.{decimals}f} {suffix}'
    f'{"iB" if use_binary and suffix.strip() else "B"}'
    )

  # Done
  LOG.debug('string: %s', size_str)
  return size_str


def sleep(seconds: int | float = 2) -> None:
  """Simple wrapper for time.sleep."""
  time.sleep(seconds)


def string_to_bytes(size: float | int | str, assume_binary: bool = False) -> int:
  """Convert human-readable size to bytes and return an int."""
  LOG.debug('size: %s, assume_binary: %s', size, assume_binary)
  scale = 1000
  size = str(size)

  # Check if given a bare number (no units)
  try:
    tmp = float(size)
  except ValueError:
    # Ignore and try parsing below
    pass
  else:
    # Return as int assuming input value was in bytes
    size = int(tmp)
    LOG.debug('bytes: %s', size)
    return size

  # Parse string
  tmp = REGEX_SIZE_STRING.search(size.upper())
  if not tmp:
    raise ValueError(f'Invalid size string: {size}')

  # Set scale
  if tmp.group('binary') or assume_binary:
    scale = 1024

  # Convert to bytes
  size = float(tmp.group('size'))
  units = tmp.group('units')
  if units == 'P':
    size *= scale ** 5
  if units == 'T':
    size *= scale ** 4
  elif units == 'G':
    size *= scale ** 3
  elif units == 'M':
    size *= scale ** 2
  elif units == 'K':
    size *= scale ** 1
  elif units == 'B':
    size *= scale ** 0
  size = int(size)

  # Done
  LOG.debug('bytes: %s', size)
  return size


if __name__ == '__main__':
  print("This file is not meant to be called directly.")
