from . import duration
from . import memory


class String:
    r"""
    Rich string.

    .. note::

        All options are attributes, that can be modified at all times.

    :options:

        **data** (``<str>`` | ``None``)
            The data.

        **width** ([``None``] | ``<int>``)
            Print width (formatted print only).

        **color** ([``None``] | ``<str>``)
            Print color, e.g. "1;32" for bold green (formatted print only).

        **align** ([``'<'``] | ``'>'``)
            Print alignment (formatted print only).

        **dummy** ([``0``] | ``<int>`` | ``<float>``)
            Dummy numerical value.

    :methods:

        **A.format()**
            Formatted string.

        **str(A)**
            Unformatted string.

        **A.isnumeric()**
            Return if the "data" is numeric.

        **int(A)**
            Dummy integer.

        **float(A)**
            Dummy float.
    """

    def __init__(self, data, width=None, align="<", color=None, dummy=0):

        self.data = data
        self.width = width
        self.color = color
        self.align = align
        self.dummy = dummy

    def format(self):
        r"""
        Return formatted string: align/width/color are applied.
        """

        if self.width and self.color:
            fmt = "\x1b[{color:s}m{{0:{align:s}{width:d}.{width:d}s}}\x1b[0m".format(
                **self.__dict__
            )
        elif self.width:
            fmt = "{{0:{align:s}{width:d}.{width:d}s}}".format(**self.__dict__)
        elif self.color:
            fmt = "\x1b[{color:s}m{{0:{align:s}s}}\x1b[0m".format(**self.__dict__)
        else:
            fmt = "{{0:{align:s}s}}".format(**self.__dict__)

        return fmt.format(str(self))

    def isnumeric(self):
        r"""
        Return if the "data" is numeric : always zero for this class.
        """

        return False

    def __str__(self):

        return str(self.data)

    def __len__(self):

        return len(str(self))

    def __int__(self):

        return int(self.dummy)

    def __float__(self):

        return float(self.dummy)

    def __repr__(self):

        return str(self)

    def __lt__(self, other):

        return str(self) < str(other)


class Integer(String):
    r"""
    Rich integer.

    .. note::

        All options are attributes, that can be modified at all times.

    :options:

        **data** (``<str>`` | ``None``)
            The data.

        **width** ([``None``] | ``<int>``)
            Print width (formatted print only).

        **color** ([``None``] | ``<str>``)
            Print color, e.g. "1;32" for bold green (formatted print only).

        **align** ([``'<'``] | ``'>'``)
            Print alignment (formatted print only).

        **dummy** ([``0``] | ``<int>`` | ``<float>``)
            Dummy numerical value, used in case of non-numerical ``data``.

    :methods:

        **A.format()**
            Formatted string.

        **str(A)**
            Unformatted string.

        **A.isnumeric()**
            Return if the "data" is numeric.

        **int(A)**
            Return ``data`` as integer (``dummy`` is returned if ``data`` is not numeric).

        **float(A)**
            Return ``data`` as float (``dummy`` is returned if ``data`` is not numeric).
    """

    def __init__(self, data, **kwargs):

        try:
            data = int(data)
        except BaseException:
            pass

        super().__init__(data, **kwargs)

    def isnumeric(self):
        r"""
        Return if the "data" is numeric : always zero for this class.
        """

        if isinstance(self.data, int):
            return True
        if isinstance(self.data, float):
            return True

        return False

    def __int__(self):

        if isinstance(self.data, int):
            return self.data
        if isinstance(self.data, float):
            return int(self.data)

        return int(self.dummy)

    def __float__(self):

        if isinstance(self.data, int):
            return float(self.data)
        if isinstance(self.data, float):
            return self.data

        return float(self.dummy)

    def __lt__(self, other):

        if self.isnumeric() and other.isnumeric():
            return int(self) < int(other)
        elif self.isnumeric():
            return 0
        elif other.isnumeric():
            return -1

        return self.data < other.data


class Float(String):
    r"""
    Rich float.

    .. note::

        All options are attributes, that can be modified at all times.

    :options:

        **data** (``<str>`` | ``None``)
            The data.

        **width** ([``None``] | ``<int>``)
            Print width (formatted print only).

        **color** ([``None``] | ``<str>``)
            Print color, e.g. "1;32" for bold green (formatted print only).

        **align** ([``'<'``] | ``'>'``)
            Print alignment (formatted print only).

        **precision** ([``2``] | ``<int>``)
            Print precision (formatted print only).

        **dummy** ([``0``] | ``<int>`` | ``<float>``)
            Dummy numerical value, used in case of non-numerical ``data``.

    :methods:

        **A.format()**
            Formatted string.

        **str(A)**
            Unformatted string.

        **A.isnumeric()**
            Return if the "data" is numeric.

        **int(A)**
            Return ``data`` as integer (``dummy`` is returned if ``data`` is not numeric).

        **float(A)**
            Return ``data`` as float (``dummy`` is returned if ``data`` is not numeric).
    """

    def __init__(self, data, **kwargs):

        try:
            data = float(data)
        except BaseException:
            pass

        self.precision = kwargs.pop("precision", 2)

        super().__init__(data, **kwargs)

    def isnumeric(self):
        r"""
        Return if the "data" is numeric : always zero for this class.
        """

        if isinstance(self.data, float):
            return True
        if isinstance(self.data, int):
            return True

        return False

    def __str__(self):

        if not self.isnumeric():
            return self.data

        return "{{0:.{precision:d}f}}".format(**self.__dict__).format(self.data)

    def __int__(self):

        if isinstance(self.data, float):
            return int(self.data)
        if isinstance(self.data, int):
            return self.data

        return int(self.dummy)

    def __float__(self):

        if isinstance(self.data, float):
            return self.data
        if isinstance(self.data, int):
            return float(self.data)

        return float(self.dummy)

    def __lt__(self, other):

        if self.isnumeric() and other.isnumeric():
            return float(self) < float(other)
        elif self.isnumeric():
            return 0
        elif other.isnumeric():
            return -1

        return self.data < other.data


class Duration(Integer):
    r"""
    Rich duration (seconds).

    .. note::

        All options are attributes, that can be modified at all times.

    :options:

        **data** (``<str>`` | ``None``)
            The data.

        **width** ([``None``] | ``<int>``)
            Print width (formatted print only).

        **color** ([``None``] | ``<str>``)
            Print color, e.g. "1;32" for bold green (formatted print only).

        **align** ([``'<'``] | ``'>'``)
            Print alignment (formatted print only).

        **precision** ([``None``] | ``<int>``)
            Print precision to use for the conversion (formatted print only).
            ``None`` means automatic precision.
            See ``GooseSLURM.duration.asHuman``.

        **dummy** ([``0``] | ``<int>`` | ``<float>``)
            Dummy numerical value, used in case of non-numerical ``data``.

    :methods:

        **A.format()**
            Formatted string, after unit conversion.

        **str(A)**
            Unformatted string, after unit conversion.

        **A.isnumeric()**
            Return if the "data" is numeric.

        **int(A)**
            Return ``data`` as integer (``dummy`` is returned if ``data`` is not numeric).

        **float(A)**
            Return ``data`` as float (``dummy`` is returned if ``data`` is not numeric).
    """

    def __init__(self, data, **kwargs):

        data = duration.asSeconds(data, default=data)

        self.precision = kwargs.pop("precision", None)

        super().__init__(data, **kwargs)

    def __str__(self):

        if not self.isnumeric():
            return self.data

        return duration.asHuman(self.data, self.precision)


class Memory(Integer):
    r"""
    Rich memory (bytes).

    .. note::

        All options are attributes, that can be modified at all times.

    :options:

        **data** (``<str>`` | ``None``)
            The data.

        **width** ([``None``] | ``<int>``)
            Print width (formatted print only).

        **color** ([``None``] | ``<str>``)
            Print color, e.g. "1;32" for bold green (formatted print only).

        **align** ([``'<'``] | ``'>'``)
            Print alignment (formatted print only).

        **precision** ([``None``] | ``<int>``)
            Print precision to use for the conversion (formatted print only).
            ``None`` means automatic precision.
            See ``GooseSLURM.memory.asHuman``.

        **dummy** ([``0``] | ``<int>`` | ``<float>``)
            Dummy numerical value, used in case of non-numerical ``data``.

        **default_unit** (``int``)
            The unit to assume if no unit if specified (specify the number of bytes).

    :methods:

        **A.format()**
            Formatted string, after unit conversion.

        **str(A)**
            Unformatted string, after unit conversion.

        **A.isnumeric()**
            Return if the "data" is numeric.

        **int(A)**
            Return ``data`` as integer (``dummy`` is returned if ``data`` is not numeric).

        **float(A)**
            Return ``data`` as float (``dummy`` is returned if ``data`` is not numeric).
    """

    def __init__(self, data, **kwargs):

        if "default_unit" in kwargs:
            data = memory.asBytes(data, default=data, default_unit=kwargs.pop("default_unit"))
        else:
            data = memory.asBytes(data, default=data)

        self.precision = kwargs.pop("precision", None)

        super().__init__(data, **kwargs)

    def __str__(self):

        if not self.isnumeric():
            return self.data

        return memory.asHuman(self.data, self.precision)
