#
# Copyright (c) 2018 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. 
#

"""
  SizeString - LVM-style size strings

  $Id: //eng/vdo-releases/magnesium/src/python/vdo/vdomgmnt/SizeString.py#1 $

"""
import locale
from . import Constants

class SizeString(object):
  """Represents the size of an object such as a disk partition.

  Conversions are provided to and from suffixed size strings as used
  by LVM commands like lvcreate(8). These strings consist of a
  (possibly floating-point) number followed by an optional unit
  suffix: B (bytes), S (512-byte sectors), and KMGTPE for kilobytes
  through exabytes, respectively. Suffixes are not case-sensitive; the
  default unit is Megabytes. Currently, we reject negative sizes.

  Unlike some (but not all) LVM commands we do not interpret the upper-case
  version of a suffix as a power of ten.

  Attributes:
    _originalString (str): the original string we were constructed
      with, mainly used for debugging
    _bytes (int): the value of this object in bytes

  """

  ######################################################################
  # Public methods
  ######################################################################
  def __add__(self, rhs):
    retval = SizeString("")
    retval._bytes = self._bytes + int(rhs)
    return retval

  ######################################################################
  def __bool__(self):
    return self._bytes != 0

  ######################################################################
  def __cmp__(self, rhs):
    result = 0
    if not isinstance(rhs, SizeString):
      result = NotImplemented
    else:
      result = cmp(self._bytes, rhs.toBytes())
    return result

  ######################################################################
  def __iadd__(self, rhs):
    self._bytes += int(rhs)
    return self

  ######################################################################
  def __int__(self):
    return self._bytes

  ######################################################################
  def __nonzero__(self):
    return self.__bool__()

  ######################################################################
  def asLvmText(self):
    """Returns this object as a size string without a decimal point."""
    suffix = Constants.lvmDefaultSuffix
    size = self._bytes
    if size > 0:
      for click in Constants.lvmSiSuffixes[::-1]:
        divisor = Constants.lvmSiSuffixSizeMap[click]
        if size % divisor == 0:
          size = size // divisor
          suffix = click
          break
      else:
          suffix = Constants.lvmByteSuffix

    return str(size) + suffix.upper()

  ######################################################################
  def roundToBlock(self):
    """Rounds this object down to a multiple of the block size."""
    self._bytes = self.toBlocks() * Constants.VDO_BLOCK_SIZE
    return self

  ######################################################################
  def toBlocks(self):
    """Returns this object as a count of 4K blocks, rounding down."""
    return (self._bytes // Constants.VDO_BLOCK_SIZE)

  ######################################################################
  def toBytes(self):
    """Returns the count of bytes represented by this object."""
    return self._bytes

  ######################################################################
  def toSectors(self):
    """Returns this object as a count of 512-byte sectors, rounding up."""
    bytesPerSector = Constants.lvmSuffixSizeMap[Constants.lvmSectorSuffix]
    return (self._bytes + (bytesPerSector - 1)) / bytesPerSector

  ######################################################################
  # Overridden methods
  ######################################################################
  def __init__(self, sz):
    self._originalString = sz
    if sz:
      try:
        suffix = sz[-1:].lower()
        if suffix in Constants.lvmSuffixes:
          nbytes = self._atof(sz[:-1])
        else:
          nbytes = self._atof(sz)
          suffix = Constants.lvmDefaultSuffix
      except ValueError:
        raise ValueError(_("invalid size string \"{size}\"").format(size=sz))
      nbytes *= float(Constants.lvmSuffixSizeMap[suffix])
      self._bytes = int(nbytes)
      if self._bytes < 0:
        raise ValueError(_("invalid size string \"{size}\"").format(size=sz))
    else:
      self._bytes = 0

  ######################################################################
  def __repr__(self):
    return "{0} ({1}{2})".format(self._originalString,
                                 self._bytes,
                                 Constants.lvmByteSuffix.upper())

  ######################################################################
  def __str__(self):
    return self.asLvmText()

  ######################################################################
  # Protected methods
  ######################################################################
  @staticmethod
  def _atof(s):
    """Tries to convert a float using the current LC_NUMERIC settings.
    If something goes wrong, tries float().
    """
    try:
      return locale.atof(s)
    except Exception:
      return float(s)
