# Copyright (c) 2016-2017 Satlab Aps <satlab@satlab.com>
# All rights reserved. Do not distribute without permission.

import os
import ctypes

# Load test library
testlib = ctypes.CDLL(os.environ['SL_TESTLIB'])

# typedef uint16_t prop_id_t
prop_id_t = ctypes.c_uint16

# typedef enum prop_type_t
prop_type_t = ctypes.c_int

#struct sl_prop_query_element {
#	prop_id_t id;
#	const void *value;
#	size_t size;
#};
class sl_prop_query_element(ctypes.Structure):
    _fields_ = [
        ('id', prop_id_t),
        ('value', ctypes.c_void_p),
        ('size', ctypes.c_size_t),
    ]


# #define SL_PROP_QUERY_MAX_ELEMENTS 32
SL_PROP_QUERY_MAX_ELEMENTS = 32


#struct sl_prop_query {
#	enum sl_prop_query_type type;
#	uint8_t flags;
#	uint8_t *txbuf;
#	size_t txbufsize;
#	uint8_t *rxbuf;
#	size_t rxbufsize;
#	bool allocated;
#	size_t txsize;
#	size_t rxsize;
#	size_t chunksize;
#	struct sl_prop_query_element element[SL_PROP_QUERY_MAX_ELEMENTS];
#	size_t elements;
#};
class sl_prop_query(ctypes.Structure):
    _fields_ = [
        ('type', ctypes.c_int),
        ('flags', ctypes.c_uint8),
        ('txbuf', ctypes.POINTER(ctypes.c_uint8)),
        ('txbufsize', ctypes.c_size_t),
        ('rxbuf', ctypes.POINTER(ctypes.c_uint8)),
        ('rxbufsize', ctypes.c_size_t),
        ('allocated', ctypes.c_bool),
        ('txsize', ctypes.c_size_t),
        ('rxsize', ctypes.c_size_t),
        ('chunksize', ctypes.c_size_t),
        ('element', sl_prop_query_element * SL_PROP_QUERY_MAX_ELEMENTS),
        ('elements', ctypes.c_size_t),
    ]


# Opaque for now
class prop_subsys_spec(ctypes.Structure):
    pass


class prop_spec(ctypes.Structure):
    pass


class PropException(Exception):
    pass


def nullcheck(result, func, args):
    if not result:
        raise PropException('Unknown property \'{}\''.format(args[1].value.decode('ascii')))
    return result


def propcheck(result, func, args, action):
    if result < 0:
        errmsg = 'Failed to {} property with ID {:#04x}: {}'.format(action, args[2], os.strerror(-result))
        raise PropException(errmsg)
    return result


def propcheck_get(result, func, args):
    return propcheck(result, func, args, 'get')


def propcheck_set(result, func, args):
    return propcheck(result, func, args, 'set')


def negcheck(result, func, args):
    if result < 0:
        raise PropException(f'function returned {result}: {os.strerror(-result)}')
    return result


# const struct prop_spec *sl_prop_spec_find_by_name(const struct prop_subsys_spec *spec, const char *name)
_sl_prop_spec_find_by_name = testlib.sl_prop_spec_find_by_name
_sl_prop_spec_find_by_name.argtypes = [ctypes.POINTER(prop_subsys_spec), ctypes.c_char_p]
_sl_prop_spec_find_by_name.restype = ctypes.POINTER(prop_spec)
_sl_prop_spec_find_by_name.errcheck = nullcheck

# prop_id_t sl_prop_remote_id(const struct prop_spec *spec)
_sl_prop_remote_id = testlib.sl_prop_remote_id
_sl_prop_remote_id.argtypes = [ctypes.POINTER(prop_spec)]
_sl_prop_remote_id.restype = prop_id_t

# prop_type_t sl_prop_remote_id(const struct prop_spec *spec)
_sl_prop_remote_type = testlib.sl_prop_remote_type
_sl_prop_remote_type.argtypes = [ctypes.POINTER(prop_spec)]
_sl_prop_remote_type.restype = prop_type_t

# int sl_prop_remote_get_int8(uint8_t node, uint32_t timeout, prop_id_t id, int8_t *val);
_sl_prop_remote_get_int8 = testlib.sl_prop_remote_get_int8
_sl_prop_remote_get_int8.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_int8)]
_sl_prop_remote_get_int8.restype = ctypes.c_int
_sl_prop_remote_get_int8.errcheck = propcheck_get

# int sl_prop_remote_set_int8(uint8_t node, uint32_t timeout, prop_id_t id, int8_t val);
_sl_prop_remote_set_int8 = testlib.sl_prop_remote_set_int8
_sl_prop_remote_set_int8.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_int8]
_sl_prop_remote_set_int8.restype = ctypes.c_int
_sl_prop_remote_set_int8.errcheck = propcheck_set

# int sl_prop_remote_get_uint8(uint8_t node, uint32_t timeout, prop_id_t id, uint8_t *val);
_sl_prop_remote_get_uint8 = testlib.sl_prop_remote_get_uint8
_sl_prop_remote_get_uint8.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_uint8)]
_sl_prop_remote_get_uint8.restype = ctypes.c_int
_sl_prop_remote_get_uint8.errcheck = propcheck_get

# int sl_prop_remote_set_uint8(uint8_t node, uint32_t timeout, prop_id_t id, uint8_t val);
_sl_prop_remote_set_uint8 = testlib.sl_prop_remote_set_uint8
_sl_prop_remote_set_uint8.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_uint8]
_sl_prop_remote_set_uint8.restype = ctypes.c_int
_sl_prop_remote_set_uint8.errcheck = propcheck_set

# int sl_prop_remote_get_int16(uint8_t node, uint32_t timeout, prop_id_t id, int16_t *val);
_sl_prop_remote_get_int16 = testlib.sl_prop_remote_get_int16
_sl_prop_remote_get_int16.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_int16)]
_sl_prop_remote_get_int16.restype = ctypes.c_int
_sl_prop_remote_get_int16.errcheck = propcheck_get

# int sl_prop_remote_set_int16(uint8_t node, uint32_t timeout, prop_id_t id, int16_t val);
_sl_prop_remote_set_int16 = testlib.sl_prop_remote_set_int16
_sl_prop_remote_set_int16.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_int16]
_sl_prop_remote_set_int16.restype = ctypes.c_int
_sl_prop_remote_set_int16.errcheck = propcheck_set

# int sl_prop_remote_get_uint16(uint8_t node, uint32_t timeout, prop_id_t id, uint16_t *val);
_sl_prop_remote_get_uint16 = testlib.sl_prop_remote_get_uint16
_sl_prop_remote_get_uint16.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_uint16)]
_sl_prop_remote_get_uint16.restype = ctypes.c_int
_sl_prop_remote_get_uint16.errcheck = propcheck_get

# int sl_prop_remote_set_uint16(uint8_t node, uint32_t timeout, prop_id_t id, uint16_t val);
_sl_prop_remote_set_uint16 = testlib.sl_prop_remote_set_uint16
_sl_prop_remote_set_uint16.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_uint16]
_sl_prop_remote_set_uint16.restype = ctypes.c_int
_sl_prop_remote_set_uint16.errcheck = propcheck_set

# int sl_prop_remote_get_int32(uint8_t node, uint32_t timeout, prop_id_t id, int32_t *val);
_sl_prop_remote_get_int32 = testlib.sl_prop_remote_get_int32
_sl_prop_remote_get_int32.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_int32)]
_sl_prop_remote_get_int32.restype = ctypes.c_int
_sl_prop_remote_get_int32.errcheck = propcheck_get

# int sl_prop_remote_set_int32(uint8_t node, uint32_t timeout, prop_id_t id, int32_t val);
_sl_prop_remote_set_int32 = testlib.sl_prop_remote_set_int32
_sl_prop_remote_set_int32.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_int32]
_sl_prop_remote_set_int32.restype = ctypes.c_int
_sl_prop_remote_set_int32.errcheck = propcheck_set

# int sl_prop_remote_get_uint32(uint8_t node, uint32_t timeout, prop_id_t id, uint32_t *val);
_sl_prop_remote_get_uint32 = testlib.sl_prop_remote_get_uint32
_sl_prop_remote_get_uint32.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_uint32)]
_sl_prop_remote_get_uint32.restype = ctypes.c_int
_sl_prop_remote_get_uint32.errcheck = propcheck_get

# int sl_prop_remote_set_uint32(uint8_t node, uint32_t timeout, prop_id_t id, uint32_t val);
_sl_prop_remote_set_uint32 = testlib.sl_prop_remote_set_uint32
_sl_prop_remote_set_uint32.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_uint32]
_sl_prop_remote_set_uint32.restype = ctypes.c_int
_sl_prop_remote_set_uint32.errcheck = propcheck_set

# int sl_prop_remote_get_int64(uint8_t node, uint32_t timeout, prop_id_t id, int64_t *val);
_sl_prop_remote_get_int64 = testlib.sl_prop_remote_get_int64
_sl_prop_remote_get_int64.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_int64)]
_sl_prop_remote_get_int64.restype = ctypes.c_int
_sl_prop_remote_get_int64.errcheck = propcheck_get

# int sl_prop_remote_set_int64(uint8_t node, uint32_t timeout, prop_id_t id, int64_t val);
_sl_prop_remote_set_int64 = testlib.sl_prop_remote_set_int64
_sl_prop_remote_set_int64.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_int64]
_sl_prop_remote_set_int64.restype = ctypes.c_int
_sl_prop_remote_set_int64.errcheck = propcheck_set

# int sl_prop_remote_get_uint64(uint8_t node, uint32_t timeout, prop_id_t id, uint64_t *val);
_sl_prop_remote_get_uint64 = testlib.sl_prop_remote_get_uint64
_sl_prop_remote_get_uint64.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_uint64)]
_sl_prop_remote_get_uint64.restype = ctypes.c_int
_sl_prop_remote_get_uint64.errcheck = propcheck_get

# int sl_prop_remote_set_uint64(uint8_t node, uint32_t timeout, prop_id_t id, uint64_t val);
_sl_prop_remote_set_uint64 = testlib.sl_prop_remote_set_uint64
_sl_prop_remote_set_uint64.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_uint64]
_sl_prop_remote_set_uint64.restype = ctypes.c_int
_sl_prop_remote_set_uint64.errcheck = propcheck_set

# int sl_prop_remote_get_float(uint8_t node, uint32_t timeout, prop_id_t id, float *val);
_sl_prop_remote_get_float = testlib.sl_prop_remote_get_float
_sl_prop_remote_get_float.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_float)]
_sl_prop_remote_get_float.restype = ctypes.c_int
_sl_prop_remote_get_float.errcheck = propcheck_get

# int sl_prop_remote_set_float(uint8_t node, uint32_t timeout, prop_id_t id, float val);
_sl_prop_remote_set_float = testlib.sl_prop_remote_set_float
_sl_prop_remote_set_float.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_float]
_sl_prop_remote_set_float.restype = ctypes.c_int
_sl_prop_remote_set_float.errcheck = propcheck_set

# int sl_prop_remote_get_double(uint8_t node, uint32_t timeout, prop_id_t id, double *val);
_sl_prop_remote_get_double = testlib.sl_prop_remote_get_double
_sl_prop_remote_get_double.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.POINTER(ctypes.c_double)]
_sl_prop_remote_get_double.restype = ctypes.c_int
_sl_prop_remote_get_double.errcheck = propcheck_get

# int sl_prop_remote_set_double(uint8_t node, uint32_t timeout, prop_id_t id, double val);
_sl_prop_remote_set_double = testlib.sl_prop_remote_set_double
_sl_prop_remote_set_double.argtypes = [ctypes.c_uint8, ctypes.c_uint32, prop_id_t, ctypes.c_double]
_sl_prop_remote_set_double.restype = ctypes.c_int
_sl_prop_remote_set_double.errcheck = propcheck_set

# int sl_prop_remote_query_create(struct sl_prop_query *query, size_t txsize, size_t rxsize);
_sl_prop_remote_query_create = testlib.sl_prop_remote_query_create
_sl_prop_remote_query_create.argtypes = [ctypes.POINTER(sl_prop_query), ctypes.c_size_t, ctypes.c_size_t]
_sl_prop_remote_query_create.restype = ctypes.c_int
_sl_prop_remote_query_create.errcheck = negcheck

# int sl_prop_remote_query_destroy(struct sl_prop_query *query);
_sl_prop_remote_query_destroy = testlib.sl_prop_remote_query_destroy
_sl_prop_remote_query_destroy.argtypes = [ctypes.POINTER(sl_prop_query)]
_sl_prop_remote_query_destroy.restype = ctypes.c_int
_sl_prop_remote_query_destroy.errcheck = negcheck

# int sl_prop_remote_query_get(struct sl_prop_query *query, prop_id_t id);
_sl_prop_remote_query_get = testlib.sl_prop_remote_query_get
_sl_prop_remote_query_get.argtypes = [ctypes.POINTER(sl_prop_query), prop_id_t]
_sl_prop_remote_query_get.restype = ctypes.c_int
_sl_prop_remote_query_get.errcheck = negcheck

# int sl_prop_remote_query_send(struct sl_prop_query *query, uint8_t node, uint32_t timeout);
_sl_prop_remote_query_send = testlib.sl_prop_remote_query_send
_sl_prop_remote_query_send.argtypes = [ctypes.POINTER(sl_prop_query), ctypes.c_uint8, ctypes.c_uint32]
_sl_prop_remote_query_send.restype = ctypes.c_int
_sl_prop_remote_query_send.errcheck = negcheck

# int sl_prop_remote_query_get_reply(struct sl_prop_query *query, prop_id_t id, void *value, size_t valsize);
_sl_prop_remote_query_get_reply = testlib.sl_prop_remote_query_get_reply
_sl_prop_remote_query_get_reply.argtypes = [ctypes.POINTER(sl_prop_query), prop_id_t, ctypes.c_void_p, ctypes.c_size_t]
_sl_prop_remote_query_get_reply.restype = ctypes.c_int
_sl_prop_remote_query_get.errcheck = negcheck


class PropertyQuery():
    def __init__(self, propnode, txsize=1024, rxsize=1024, chunksize=None):

        self.propnode = propnode
        self.q = sl_prop_query()
        _sl_prop_remote_query_create(self.q, txsize, rxsize)
        self.props = []

        if chunksize is not None:
            self.q.chunksize = chunksize

    def __del__(self):
        _sl_prop_remote_query_destroy(self.q)

    def get(self, names):
        if not type(names) is list:
            names = [names]
        for name in names:
            name_p = ctypes.c_char_p(name.encode('ascii'))
            prop = _sl_prop_spec_find_by_name(self.propnode.spec, name_p)
            pid = _sl_prop_remote_id(prop)
            proptype = _sl_prop_remote_type(prop)
            cproptype, _, _ = self.propnode._ctype_from_proptype(proptype)

            self.props.append((name, prop, pid, cproptype))

            _sl_prop_remote_query_get(self.q, pid)

        return self

    def set(self, names):
        if type(names) is not list:
            names = [names]
        for name in names:
            name_p = ctypes.c_char_p(name.encode('ascii'))
            prop = self.propnode._sl_prop_spec_find_by_name(self.propnode.spec, name_p)
            pid = self.propnode._sl_prop_remote_id(prop)
            proptype = self.propnode._sl_prop_remote_type(prop)
            cproptype, _, _ = self.propnode._ctype_from_proptype(proptype)

            self.props.append((name, prop, pid, cproptype))

            _sl_prop_remote_query_get(self.q, pid)

        return self

    def send(self):
        _sl_prop_remote_query_send(self.q, self.propnode.addr, self.propnode.timeout)

        res = {}

        for p in self.props:
            val = p[3]()
            _sl_prop_remote_query_get_reply(self.q, p[2], ctypes.byref(val), ctypes.sizeof(val))
            res[p[0]] = val.value

        return res


class PropertyNode():
    PROP_TYPE_BOOL = 1
    PROP_TYPE_UINT8 = 2
    PROP_TYPE_UINT16 = 3
    PROP_TYPE_UINT32 = 4
    PROP_TYPE_UINT64 = 5
    PROP_TYPE_INT8 = 6
    PROP_TYPE_INT16 = 7
    PROP_TYPE_INT32 = 8
    PROP_TYPE_INT64 = 9
    PROP_TYPE_DOUBLE = 10
    PROP_TYPE_FLOAT = 11
    PROP_TYPE_STRING = 12
    PROP_TYPE_BINARY = 13
    PROP_TYPE_ARRAY = 14

    def __init__(self, addr, spec, timeout=1000):
        self.addr = addr
        self.timeout = timeout
        self.spec = spec

    def _ctype_from_proptype(self, proptype):
        if proptype == self.PROP_TYPE_BOOL:
            return ctypes.c_bool, _sl_prop_remote_get_bool, _sl_prop_remote_set_bool
        elif proptype == self.PROP_TYPE_UINT8:
            return ctypes.c_uint8, _sl_prop_remote_get_uint8, _sl_prop_remote_set_uint8
        elif proptype == self.PROP_TYPE_UINT16:
            return ctypes.c_uint16, _sl_prop_remote_get_uint16, _sl_prop_remote_set_uint16
        elif proptype == self.PROP_TYPE_UINT32:
            return ctypes.c_uint32, _sl_prop_remote_get_uint32, _sl_prop_remote_set_uint32
        elif proptype == self.PROP_TYPE_UINT64:
            return ctypes.c_uint64,  _sl_prop_remote_get_uint64, _sl_prop_remote_set_uint64
        elif proptype == self.PROP_TYPE_INT8:
            return ctypes.c_int8, _sl_prop_remote_get_int8, _sl_prop_remote_set_int8
        elif proptype == self.PROP_TYPE_INT16:
            return ctypes.c_int16, _sl_prop_remote_get_int16, _sl_prop_remote_set_int16
        elif proptype == self.PROP_TYPE_INT32:
            return ctypes.c_int32, _sl_prop_remote_get_int32, _sl_prop_remote_set_int32
        elif proptype == self.PROP_TYPE_INT64:
            return ctypes.c_int64, _sl_prop_remote_get_int64, _sl_prop_remote_set_int64
        elif proptype == self.PROP_TYPE_DOUBLE:
            return ctypes.c_double, _sl_prop_remote_get_double, _sl_prop_remote_set_double
        elif proptype == self.PROP_TYPE_FLOAT:
            return ctypes.c_float, _sl_prop_remote_get_float, _sl_prop_remote_set_float
        else:
            raise PropException('Unknown property type: {}'.format(proptype))

    def query(self, chunksize=None):
        return PropertyQuery(self, chunksize=chunksize)

    def get(self, names):
        if type(names) is not list:
            name = names
            name_p = ctypes.c_char_p(name.encode('ascii'))
            prop = _sl_prop_spec_find_by_name(self.spec, name_p)
            pid = _sl_prop_remote_id(prop)
            proptype = _sl_prop_remote_type(prop)
            result, getter, _ = self._ctype_from_proptype(proptype)
            val = result()
            getter(self.addr, self.timeout, pid, ctypes.byref(val))
            return val.value
        else:
            q = self.query()
            q.get(names)
            reply = q.send()
            return reply.values()

    def set(self, name, value):
        name_p = ctypes.c_char_p(name.encode('ascii'))
        prop = _sl_prop_spec_find_by_name(self.spec, name_p)
        pid = _sl_prop_remote_id(prop)
        proptype = _sl_prop_remote_type(prop)
        result, _, setter = self._ctype_from_proptype(proptype)
        val = result(value)
        setter(self.addr, self.timeout, pid, val)

    def __getitem__(self, key):
        return self.get(key)

    def __setitem__(self, key, value):
        return self.set(key, value)
