from empire.core import *
from empire.enums.complex_enum import ComplexEnum


TypeUnit = Tuple[int, str, str, int]
TypeUnitIndex: Final[int] = 0
TypeUnitFullName: Final[int] = 1
TypeUnitAbbr: Final[int] = 2
TypeUnitThreshold: Final[int] = 3


class DataSizeUnitsEnum(ComplexEnum):
    BYTE: Final[TypeUnit] = (0, 'byte', 'B', 1024)
    KILO: Final[TypeUnit] = (1, 'kilobyte', 'kB', 1024)
    MEGA: Final[TypeUnit] = (2, 'megabyte', 'MB', 1024)
    GIGA: Final[TypeUnit] = (3, 'gigabyte', 'GB', 1024)
    TERA: Final[TypeUnit] = (4, 'terabyte', 'TB', -1)


class TimeUnitsEnums(ComplexEnum):
    NANOS: Final[TypeUnit] = (0, 'nanoseconds', 'ns', 1000)
    MICROS: Final[TypeUnit] = (1, 'microseconds', 'µs', 1000)
    MILLIS: Final[TypeUnit] = (2, 'milliseconds', 'ms', 1000)
    SECOND: Final[TypeUnit] = (3, 'seconds', 's', 60)
    MINUTES: Final[TypeUnit] = (4, 'minutes', 'min', 60)
    HOURS: Final[TypeUnit] = (5, 'hours', 'h', 24)
    DAYS: Final[TypeUnit] = (6, 'days', 'd', -1)


class UnitsUtil:
    @staticmethod
    def data_size_convert_to(value: Union[int, float], unit: TypeUnit, to: TypeUnit) -> Union[int, float]:
        from_coeff: int = unit[0]
        to_coeff: int = to[0]

        if from_coeff < to_coeff:  # convert to higher unit
            return value / pow(1024, to_coeff - from_coeff)
        else:
            return value * pow(1024, from_coeff - to_coeff)
    
    @staticmethod
    def simplify(value: Union[int, float], unit: TypeUnit, keep_float: Union[Callable, None] = None) -> Tuple[Union[int, float], TypeUnit]:
        if unit[TypeUnitThreshold] == -1 or value < unit[TypeUnitThreshold]:
            if keep_float:
                return value, unit
            else:
                return keep_float(value), unit
        else:
            value /= unit[TypeUnitThreshold]
            return UnitsUtil.simplify(value, TimeUnitsEnums.parse_ordinal(unit[TypeUnitIndex] + 1), keep_float)
    
    @staticmethod
    def time_convert_to_best(value: Union[int, float], unit: TypeUnit) -> Tuple[Union[int, float], TypeUnit]:
        threshold: int = 1000 if unit[0] < 3 else 60
        if value >= threshold:
            value /= threshold
            if unit == TimeUnitsEnums.HOURS:
                return value, TimeUnitsEnums.HOURS
            else:
                return UnitsUtil.time_convert_to_best(value, TimeUnitsEnums.parse_int_to_tuple(unit[0] + 1))
        else:
            return value, unit

    @staticmethod
    def get_full_name(unit_enum_value: TypeUnit) -> str:
        return unit_enum_value[1]

    @staticmethod
    def get_abbr(unit_enum_value: TypeUnit) -> str:
        return unit_enum_value[2]


if __name__ == '__main__':
    assert UnitsUtil.data_size_convert_to(1024, DataSizeUnitsEnum.BYTE, DataSizeUnitsEnum.KILO) == 1.0
    assert UnitsUtil.data_size_convert_to(1536, DataSizeUnitsEnum.KILO, DataSizeUnitsEnum.MEGA) == 1.5
    assert UnitsUtil.data_size_convert_to(1, DataSizeUnitsEnum.MEGA, DataSizeUnitsEnum.BYTE) == 1024 * 1024

    assert UnitsUtil.time_convert_to_best(800, TimeUnitsEnums.MILLIS)[0] == 800
    assert UnitsUtil.time_convert_to_best(800, TimeUnitsEnums.MILLIS)[1][0] == 2
    assert UnitsUtil.time_convert_to_best(2500, TimeUnitsEnums.NANOS)[0] == 2.5
    assert UnitsUtil.time_convert_to_best(2500, TimeUnitsEnums.NANOS)[1][0] == 1

from empire.core import *
from empire.enums.complex_enum import ComplexEnum


TypeUnit = Tuple[int, str, str, int]
TypeUnitIndex: Final[int] = 0
TypeUnitFullName: Final[int] = 1
TypeUnitAbbr: Final[int] = 2
TypeUnitThreshold: Final[int] = 3


class DataSizeUnitsEnum(ComplexEnum):
    BYTE: Final[TypeUnit] = (0, 'byte', 'B', 1024)
    KILO: Final[TypeUnit] = (1, 'kilobyte', 'kB', 1024)
    MEGA: Final[TypeUnit] = (2, 'megabyte', 'MB', 1024)
    GIGA: Final[TypeUnit] = (3, 'gigabyte', 'GB', 1024)
    TERA: Final[TypeUnit] = (4, 'terabyte', 'TB', -1)


class TimeUnitsEnums(ComplexEnum):
    NANOS: Final[TypeUnit] = (0, 'nanoseconds', 'ns', 1000)
    MICROS: Final[TypeUnit] = (1, 'microseconds', 'µs', 1000)
    MILLIS: Final[TypeUnit] = (2, 'milliseconds', 'ms', 1000)
    SECOND: Final[TypeUnit] = (3, 'seconds', 's', 60)
    MINUTES: Final[TypeUnit] = (4, 'minutes', 'min', 60)
    HOURS: Final[TypeUnit] = (5, 'hours', 'h', 24)
    DAYS: Final[TypeUnit] = (6, 'days', 'd', -1)


class UnitsUtil:
    @staticmethod
    def data_size_convert_to(value: Union[int, float], unit: TypeUnit, to: TypeUnit) -> Union[int, float]:
        from_coeff: int = unit[0]
        to_coeff: int = to[0]

        if from_coeff < to_coeff:  # convert to higher unit
            return value / pow(1024, to_coeff - from_coeff)
        else:
            return value * pow(1024, from_coeff - to_coeff)
    
    @staticmethod
    def simplify(value: Union[int, float], unit: TypeUnit, keep_float: Union[Callable, None] = None) -> Tuple[Union[int, float], TypeUnit]:
        if unit[TypeUnitThreshold] == -1 or value < unit[TypeUnitThreshold]:
            if keep_float:
                return value, unit
            else:
                return keep_float(value), unit
        else:
            value /= unit[TypeUnitThreshold]
            return UnitsUtil.simplify(value, TimeUnitsEnums.parse_ordinal(unit[TypeUnitIndex] + 1), keep_float)
    
    @staticmethod
    def time_convert_to_best(value: Union[int, float], unit: TypeUnit) -> Tuple[Union[int, float], TypeUnit]:
        threshold: int = 1000 if unit[0] < 3 else 60
        if value >= threshold:
            value /= threshold
            if unit == TimeUnitsEnums.HOURS:
                return value, TimeUnitsEnums.HOURS
            else:
                return UnitsUtil.time_convert_to_best(value, TimeUnitsEnums.parse_int_to_tuple(unit[0] + 1))
        else:
            return value, unit

    @staticmethod
    def get_full_name(unit_enum_value: TypeUnit) -> str:
        return unit_enum_value[1]

    @staticmethod
    def get_abbr(unit_enum_value: TypeUnit) -> str:
        return unit_enum_value[2]


if __name__ == '__main__':
    assert UnitsUtil.data_size_convert_to(1024, DataSizeUnitsEnum.BYTE, DataSizeUnitsEnum.KILO) == 1.0
    assert UnitsUtil.data_size_convert_to(1536, DataSizeUnitsEnum.KILO, DataSizeUnitsEnum.MEGA) == 1.5
    assert UnitsUtil.data_size_convert_to(1, DataSizeUnitsEnum.MEGA, DataSizeUnitsEnum.BYTE) == 1024 * 1024

    assert UnitsUtil.time_convert_to_best(800, TimeUnitsEnums.MILLIS)[0] == 800
    assert UnitsUtil.time_convert_to_best(800, TimeUnitsEnums.MILLIS)[1][0] == 2
    assert UnitsUtil.time_convert_to_best(2500, TimeUnitsEnums.NANOS)[0] == 2.5
    assert UnitsUtil.time_convert_to_best(2500, TimeUnitsEnums.NANOS)[1][0] == 1

# convert to higher unit