#ifndef SYSVARS_H
#define SYSVARS_H

#include <stddef.h>
#include <stdbool.h>
#include <inttypes.h>

#ifndef extern_c
# ifdef __cplusplus
#  define extern_c extern "C"
# else
#  define extern_c extern
# endif
#endif

typedef union sysvar_value {
    bool v_bool;
    long int v_int;
    unsigned long int v_uint;
    const char *v_str;
    const char *v_tz;		/* Timezone */
    uint32_t v_ip;		/* IPv4 address */
    const uint8_t *v_mac;	/* MAC address */
    void *v_ptr;
} sysvar_t;

struct sysvar_ops {
    bool (*set)(sysvar_t *, sysvar_t);
    /* bool (*unset)(sysvar_t *); - not used */
    const char * (*tostr)(sysvar_t, char *);
    bool (*fromstr)(sysvar_t *, const char *);
    void (*update)(sysvar_t, bool); /* Called after set or fromstr; bool = isset */
    size_t (*datasize)(sysvar_t);   /* If pointer, return size of data */
    size_t buflen;		/* Minimal buffer size for string if needed */
};

typedef const struct sysvar_ops *sysvar_type_t;

extern_c const struct sysvar_ops sysvar_bool_ops;
extern_c const struct sysvar_ops sysvar_int_ops;
extern_c const struct sysvar_ops sysvar_uint_ops;
extern_c const struct sysvar_ops sysvar_str_ops;
extern_c const struct sysvar_ops sysvar_tz_ops;
extern_c const struct sysvar_ops sysvar_ip_ops;
extern_c const struct sysvar_ops sysvar_mac_ops;

#define SYSVAR_NULLTYPE NULL
#define SYSVAR_TYPE(x) (&sysvar ## x ## _ops)

#include "sysvars_gen.h"

typedef struct sysvar_namespace {
    const char *name;
    enum sysvar_enum first;
} sysvar_ns_t;

extern_c const sysvar_ns_t sysvar_ns[(size_t)sysvar_nscount+1];
extern_c enum sysvar_enum sysvar_changed;
extern_c bool sysvar_print_updates;

/* Buffer size needed to represent some data types */
#define BOOL_BUFLEN 2
#define INT_BUFLEN  (3*sizeof(unsigned int)+2)
#define UINT_BUFLEN INT_BUFLEN
#define IP_BUFLEN   (4*4)
#define MAC_BUFLEN  (3*6)

#define SYSVAR_BUFLEN	32	/* Conservative minimum */

extern_c sysvar_t getvar(size_t var);
extern_c bool setvar(size_t var, sysvar_t val);
extern_c bool unsetvar(size_t var);
extern_c const char *getvar_tostr(size_t var);
extern_c const char *getvar_tostr_r(size_t var, char *buf);
extern_c bool setvar_fromstr(size_t var, const char *str);
extern_c void sysvar_init(void);
extern_c void sysvar_reset(size_t ns);
extern_c size_t sysvar_find(size_t ns, const char *name);
extern_c size_t sysvar_marshall(enum sysvar_enum first, size_t count,
				void *buf, size_t *buflen, uintptr_t extaddr);

/* Type-specific definitions/getters/setters */
/* Note that t contains a leading underscore to avoid bool/_Bool issues */

#define const_assert(cond, str)					     \
    do {							     \
	extern void fail(void) __attribute__((error(str)));	     \
	if (__builtin_constant_p(cond) && !(cond))		     \
	    fail();						     \
    } while (0)

#define TRY_ASSERT_TYPE(var,t)						\
    const_assert(sysvar_type(var) == SYSVAR_TYPE(t),			\
	"invalid type for sysvar " #var)

#define SYSVAR_MKTYPE(t,c_type)						\
    static inline c_type getvar ## t (size_t var)			\
    {									\
	TRY_ASSERT_TYPE(var,t);						\
	/* If var is constant and >= sysvar_count, TRY_ASSERT_TYPE() fails */ \
	if (__builtin_constant_p(var < (size_t)sysvar_count))		\
	    return sysvar_val[var].v ## t;				\
	return getvar(var).v ## t ;					\
    }									\
    static inline bool setvar ## t (size_t var, c_type v)		\
    {									\
	sysvar_t vv;							\
	TRY_ASSERT_TYPE(var,t);						\
	vv.v ## t = v;							\
	return setvar(var, vv);						\
    }

SYSVAR_MKTYPE(_bool, bool);
SYSVAR_MKTYPE(_int, long int);
SYSVAR_MKTYPE(_uint, unsigned long int);
SYSVAR_MKTYPE(_str, const char *);
SYSVAR_MKTYPE(_tz, const char *);
SYSVAR_MKTYPE(_ip, uint32_t);
SYSVAR_MKTYPE(_mac, const uint8_t *);

#endif /* SYSVARS_H */
