from ctypes import sizeof
from ptrace.ctypes_tools import formatUintHex16, formatUintHex32, formatWordHex
from datetime import datetime, timedelta
from os import getenv, access, X_OK, pathsep, getcwd
from os.path import join as path_join, isabs, dirname, normpath


def dumpRegs(log, regs):
    """
    Dump all registers using log callback (write one line).
    """
    width = max(len(name) for name, type in regs._fields_)
    name_format = "%% %us" % width
    for name, type in regs._fields_:
        value = getattr(regs, name)
        name = name_format % name
        if sizeof(type) == 32:
            value = formatUintHex32(value)
        elif sizeof(type) == 16:
            value = formatUintHex16(value)
        else:
            value = formatWordHex(value)
        log("%s = %s" % (name, value))


def readBits(value, bitmasks):
    """
    Extract bits from the integer value using a list of bit masks.
    bitmasks is a list of tuple (mask, text).

    >>> bitmask = (
    ...    (1, "exec"),
    ...    (2, "write"),
    ...    (4, "read"))
    ...
    >>> readBits(5, bitmask)
    ['exec', 'read']
    >>> readBits(12, bitmask)
    ['read', '8']
    """
    bitset = []
    for mask, item in bitmasks:
        if not value & mask:
            continue
        bitset.append(item)
        value = value & ~mask
    if value:
        bitset.append(str(value))
    return bitset


def formatBits(value, bitmasks, empty_text=None, format_value=str):
    """
    Format a value using a bitmask: see readBits() functions.

    >>> bitmask = (
    ...    (1, "exec"),
    ...    (2, "write"),
    ...    (4, "read"))
    ...
    >>> formatBits(5, bitmask, empty_text="no permission")
    '<exec|read> (5)'
    >>> formatBits(0, bitmask, empty_text="no permission")
    'no permission'
    """

    orig_value = value
    text = readBits(value, bitmasks)
    if text:
        text = "%s" % ("|".join(text))
        if value:
            text = "<%s> (%s)" % (text, format_value(orig_value))
        return text
    else:
        if empty_text:
            return empty_text
        else:
            return str(value)


LOCAL_TIMEZONE_OFFSET = datetime.fromtimestamp(
    0) - datetime.utcfromtimestamp(0)

# Start of UNIX timestamp (Epoch): 1st January 1970 at 00:00
UNIX_TIMESTAMP_T0 = datetime(1970, 1, 1)


def timestampUNIX(value, is_local):
    """
    Convert an UNIX (32-bit) timestamp to datetime object. Timestamp value
    is the number of seconds since the 1st January 1970 at 00:00. Maximum
    value is 2147483647: 19 january 2038 at 03:14:07.

    May raise ValueError for invalid value: value have to be in 0..2147483647.

    >>> timestampUNIX(0, False)
    datetime.datetime(1970, 1, 1, 0, 0)
    >>> timestampUNIX(1154175644.37, False)
    datetime.datetime(2006, 7, 29, 12, 20, 44, 370000)
    """
    timestamp = UNIX_TIMESTAMP_T0 + timedelta(seconds=value)
    if is_local:
        timestamp += LOCAL_TIMEZONE_OFFSET
    return timestamp


def locateProgram(program):
    """
    Locate a program using the PATH environment variable. Return the
    unchanged program value if it's not possible to get the full
    program path.
    """
    if isabs(program):
        return program
    if dirname(program):
        # ./test => $PWD/./test
        # ../python => $PWD/../python
        program = path_join(getcwd(), program)
        program = normpath(program)
        return program
    paths = getenv('PATH')
    if not paths:
        return program
    for path in paths.split(pathsep):
        filename = path_join(path, program)
        if access(filename, X_OK):
            return filename
    return program


def minmax(min_value, value, max_value):
    """
    Restrict value to [min_value; max_value]

    >>> minmax(-2, -3, 10)
    -2
    >>> minmax(-2, 27, 10)
    10
    >>> minmax(-2, 0, 10)
    0
    """
    return min(max(min_value, value), max_value)


def inverseDict(data):
    """
    Inverse a dictionary.

    >>> inverseDict({"0x10": 16, "0x20": 32}) == {32: '0x20', 16: '0x10'}
    True
    """
    result = {}
    for key, value in data.items():
        result[value] = key
    return result


def signal_to_exitcode(signum):
    """
    Converts a signal number to an exit code.
    UNIX: https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html

    >>> import signal
    >>> signal_to_exitcode(signal.SIGQUIT)
    131
    """
    return 128 + signum
