# SPDX-License-Identifier: GPL-2.0

import platform

def format_nr_positive(nr, machine_friendly):
    raw_string = '%d' % nr
    if machine_friendly:
        return raw_string
    fields = []
    for i in range(0, len(raw_string), 3):
        start_idx = max(0, len(raw_string) - i - 3)
        end_idx = len(raw_string) - i
        fields = [raw_string[start_idx:end_idx]] + fields
    return ','.join(fields)

def format_nr(nr, machine_friendly):
    if nr < 0:
        return '-' + format_nr_positive(nr * -1, machine_friendly)
    return format_nr_positive(nr, machine_friendly)

def format_sz(sz_bytes, machine_friendly):
    if machine_friendly:
        return '%d' % sz_bytes

    sz_bytes = float(sz_bytes)
    if sz_bytes == float(ulong_max):
        return 'max'
    if sz_bytes > 1<<60:
        return '%.3f EiB' % (sz_bytes / (1<<60))
    if sz_bytes > 1<<50:
        return '%.3f PiB' % (sz_bytes / (1<<50))
    if sz_bytes > 1<<40:
        return '%.3f TiB' % (sz_bytes / (1<<40))
    if sz_bytes > 1<<30:
        return '%.3f GiB' % (sz_bytes / (1<<30))
    if sz_bytes > 1<<20:
        return '%.3f MiB' % (sz_bytes / (1<<20))
    if sz_bytes > 1<<10:
        return '%.3f KiB' % (sz_bytes / (1<<10))
    return '%d B' % sz_bytes

def format_addr_range(start, end, machine_friendly):
    return '[%s, %s) (%s)' % (
            format_nr(start, machine_friendly),
            format_nr(end, machine_friendly),
            format_sz(end - start, machine_friendly))

ns_ns = 1
us_ns = 1000
ms_ns = 1000 * us_ns
sec_ns = 1000 * ms_ns
minute_ns = 60 * sec_ns
hour_ns = 60 * minute_ns
day_ns = 24 * hour_ns

nsecs_to_unit = {1: 'ns', us_ns: 'us', ms_ns: 'ms', sec_ns: 's',
        minute_ns: 'm', hour_ns: 'h', day_ns: 'd'}

def format_time_ns_min_unit(time_ns, min_unit, machine_friendly):
    if machine_friendly:
        return '%d' % time_ns

    if time_ns < 0:
        sign = '-'
        time_ns = -1 * time_ns
    else:
        sign = ''

    if time_ns >= ulong_max:
        return '%smax' % sign

    for unit_nsecs in sorted(nsecs_to_unit.keys(), reverse=True):
        if time_ns < unit_nsecs:
            continue
        if unit_nsecs == min_unit:
            if time_ns % unit_nsecs:
                return '%s%.3f %s' % (sign, time_ns / unit_nsecs,
                        nsecs_to_unit[unit_nsecs])
            else:
                return '%s%d %s' % (sign, time_ns / unit_nsecs,
                        nsecs_to_unit[unit_nsecs])

        unit_nr = int(time_ns / unit_nsecs)
        unit_str = '%s %s' % (
                format_nr(unit_nr, False), nsecs_to_unit[unit_nsecs])

        less_unit_ns = time_ns - unit_nr * unit_nsecs
        if less_unit_ns == 0:
            return '%s%s' % (sign, unit_str)
        else:
            return '%s%s %s' % (
                    sign, unit_str, format_time_ns_min_unit(
                        less_unit_ns, min_unit, False))
    return '0 ns'

def format_time_ns_exact(time_ns, machine_friendly):
    return format_time_ns_min_unit(time_ns, ns_ns, machine_friendly)

def format_time_us_exact(time_us, machine_friendly):
    return format_time_ns_exact(time_us * us_ns, machine_friendly)

def format_time_ms_exact(time_ms, machine_friendly):
    return format_time_ns_exact(time_ms * ms_ns, machine_friendly)

def format_time_ns(time_ns, machine_friendly):
    if machine_friendly:
        return '%d' % time_ns

    time_ns = float(time_ns)
    if time_ns < 0:
        sign = '-'
        time_ns = time_ns * -1
    else:
        sign = ''
    if time_ns >= ulong_max:
        return '%smax' % sign

    if time_ns >= hour_ns:
        hour = int(time_ns / hour_ns)
        hour_str = '%d h' % hour

        less_hour_ns = time_ns - (hour * hour_ns)
        if less_hour_ns == 0:
            return '%s%s' % (sign, hour_str)
        return '%s%s %s' % (
                sign, hour_str, format_time_ns(less_hour_ns, False))
    if time_ns >= minute_ns:
        if time_ns % minute_ns == 0:
            return '%s%d m' % (sign, time_ns / minute_ns)
        if time_ns % sec_ns == 0:
            return '%s%d m %d s' % (sign, time_ns / minute_ns,
                    (time_ns % minute_ns) / sec_ns)
        return '%s%d m %.3f s' % (sign, time_ns / minute_ns,
                (time_ns % minute_ns) / sec_ns)
    if time_ns >= sec_ns:
        if time_ns % sec_ns == 0:
            return '%s%d s' % (sign, time_ns / sec_ns)
        return '%s%.3f s' % (sign, time_ns / sec_ns)
    if time_ns >= ms_ns:
        if time_ns % ms_ns == 0:
            return '%s%d ms' % (sign, time_ns / ms_ns)
        return '%s%.3f ms' % (sign, time_ns / ms_ns)
    if time_ns >= us_ns:
        if time_ns % us_ns == 0:
            return '%s%d us' % (sign, time_ns / us_ns)
        return '%s%.3f us' % (sign, time_ns / us_ns)
    return '%s%d ns' % (sign, time_ns)

def format_time_us(time_us, machine_friendly):
    if machine_friendly:
        return '%d' % time_us

    return format_time_ns(time_us * 1000, machine_friendly)

def format_time_ms(time_ms, machine_friendly):
    if machine_friendly:
        return '%d' % time_ms

    return format_time_ns(time_ms * 1000000, machine_friendly)

def format_time_sec(time_sec, machine_friendly):
    if machine_friendly:
        return '%d' % time_sec

    return format_time_ns(time_sec * 1000000000, machine_friendly)

def format_ratio(ratio, machine_friendly):
    if machine_friendly:
        return '%f' % ratio

    over_percent = int(ratio * 100)
    over_percent_str = format_nr(over_percent, machine_friendly)

    under_percent = ratio * 100 % 1
    under_percent_str = ('%.7f' % under_percent).rstrip('0')
    # cut '0.' prefix
    under_percent_str = under_percent_str[2:]

    if under_percent_str == '':
        return '%s %%' % over_percent_str
    return '%s.%s %%' % (over_percent_str, under_percent_str)

def format_percent(percent, machine_friendly):
    if machine_friendly:
        return '%f' % percent

    return format_ratio(float(percent) / 100, machine_friendly)

def format_permil(permil, machine_friendly):
    if machine_friendly:
        return '%f' % permil

    return format_ratio(float(permil) / 1000, machine_friendly)

def format_bp(bp, machine_friendly):
    if machine_friendly:
        return '%f' % bp

    return format_ratio(float(bp) / 10000, machine_friendly)

def indent_lines(string, indent_width):
    return '\n'.join([' ' * indent_width + l for l in string.split('\n')])

number_types = [int, float]

try:
    # for python2
    number_types.append(long)
except:
    pass

uint_max = 2**32 - 1
ulong_max = 2**64 - 1
if platform.architecture()[0] != '64bit':
    ulong_max = 2**32 - 1

unit_to_bytes = {'B': 1,
        'K': 1 << 10, 'KB': 1 << 10, 'KiB': 1 << 10,
        'M': 1 << 20, 'MB': 1 << 20, 'MiB': 1 << 20,
        'G': 1 << 30, 'GB': 1 << 30, 'GiB': 1 << 30,
        'T': 1 << 40, 'TB': 1 << 40, 'TiB': 1 << 40,
        'P': 1 << 50, 'PB': 1 << 50, 'PiB': 1 << 50,
        'E': 1 << 60, 'EB': 1 << 60, 'EiB': 1 << 60}

def text_to_nr(txt):
    if type(txt) in number_types:
        return txt

    if txt == 'max':
        return ulong_max

    new_txt = ''.join([c for c in txt if c != ','])
    try:
        return int(new_txt)
    except:
        pass
    return float(new_txt)

def try_common_input(txt, min_val=0, max_val=ulong_max):
    'return success and number'
    if txt == 'min':
        return True, min_val
    if txt == 'max':
        return True, max_val
    try:
        return True, text_to_nr(txt)
    except:
        pass
    return False, None

def text_to_bytes(txt):
    success, number = try_common_input(txt)
    if success:
        return number

    unit = None
    if len(txt) > 3:
        unit = txt[len(txt) - 3:]
        if unit in unit_to_bytes:
            number = text_to_nr(txt[:-3])
        else:
            unit = None

    if unit == None:
        if len(txt) > 2:
            unit = txt[len(txt) - 2:]
            if unit in unit_to_bytes:
                number = text_to_nr(txt[:-2])
            else:
                unit = None

    if unit == None:
        if txt[-1] in unit_to_bytes:
            unit = txt[-1]
            number = text_to_nr(txt[:-1])
        else:
            unit = 'B'
            number  = text_to_nr(txt)

    return min(ulong_max, int(number * unit_to_bytes[unit]))

unit_to_nsecs = {'ns': ns_ns, 'us': us_ns, 'ms': ms_ns, 's': sec_ns,
        'm': minute_ns, 'h': hour_ns, 'd': day_ns}

def text_to_ns(txt):
    success, number = try_common_input(txt)
    if success:
        return number

    fields = txt.split()
    if len(fields) > 1:
        result_us = 0
        for i in range(0, len(fields), 2):
            result_us += text_to_ns(''.join(fields[i: i + 2]))
        return result_us

    if not txt[-2:] in unit_to_nsecs and not txt[-1] in unit_to_nsecs:
        return float(txt)

    unit = txt[-2:]
    if unit in ['ns', 'us', 'ms']:
        number = text_to_nr(txt[:-2])
    else:
        unit = txt[-1]
        number = text_to_nr(txt[:-1])
    return number * unit_to_nsecs[unit]

def text_to_us(txt):
    success, number = try_common_input(txt)
    if success:
        return number

    return text_to_ns(txt) / us_ns

def text_to_ms(txt):
    success, number = try_common_input(txt)
    if success:
        return number

    return text_to_us(txt) / 1000

def text_to_sec(txt):
    success, number = try_common_input(txt)
    if success:
        return number

    return text_to_ms(txt) / 1000

def text_to_ratio(txt):
    success, number = try_common_input(txt, 0.0)
    if success:
        return number

    is_percent = False
    if txt[-1] == '%':
        is_percent = True
        txt = txt[:-1]
    ratio = text_to_nr(txt)
    if is_percent:
        ratio /= 100.0
    return ratio

def text_to_bp(txt):
    success, number = try_common_input(txt, 0)
    if success:
        return number

    return text_to_ratio(txt) * 10000

def text_to_permil(txt):
    success, number = try_common_input(txt, 0)
    if success:
        return number

    return text_to_ratio(txt) * 1000

def text_to_percent(txt):
    success, number = try_common_input(txt, 0)
    if success:
        return number

    return text_to_ratio(txt) * 100

def text_to_nr_unit(txt):
    fields = txt.split()
    if len(fields) != 2:
        raise Exception('text_to_nr_unit requires two fields')
    return text_to_nr(fields[0]), fields[1]

def text_to_bool(txt):
    if type(txt) == bool:
        return txt

    true_txts = ['y', 'yes', 'true']
    false_txts = ['n', 'no', 'false']

    txt = txt.lower()
    if txt in true_txts:
        return True
    elif txt in false_txts:
        return False
    else:
        raise Exception('txt should be one of %s but %s' %
                (' '.join(true_txts + false_txts), txt))
