/*
 * Copyright (C) 2014 Philippe Gerum <rpm@xenomai.org>.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
 */
#ifndef _XENOMAI_SMOKEY_SMOKEY_H
#define _XENOMAI_SMOKEY_SMOKEY_H

#include <stdarg.h>
#include <stdbool.h>
#include <pthread.h>
#include <boilerplate/list.h>
#include <boilerplate/libc.h>
#include <copperplate/clockobj.h>
#include <xenomai/init.h>

#ifdef HAVE_FORK
#define do_fork fork
#else
#define do_fork vfork
#endif

#define SMOKEY_INT(__name) {		\
	 .name = # __name,		\
	 .parser = smokey_int,		\
	 .matched = 0,			\
	 }

#define SMOKEY_BOOL(__name) {		\
	 .name = # __name,		\
	 .parser = smokey_bool,		\
	 .matched = 0,			\
	 }

#define SMOKEY_STRING(__name) {		\
	 .name = # __name,		\
	 .parser = smokey_string,	\
	 .matched = 0,			\
	 }

#define SMOKEY_SIZE(__name) {		\
	 .name = # __name,		\
	 .parser = smokey_size,		\
	 .matched = 0,			\
	 }

#define SMOKEY_ARGLIST(__args...)  ((struct smokey_arg[]){ __args })

#define SMOKEY_NOARGS  (((struct smokey_arg[]){ { .name = NULL } }))

struct smokey_arg {
	const char *name;
	int (*parser)(const char *s,
		      struct smokey_arg *arg);
	union {
		int n_val;
		char *s_val;
		size_t l_val;
	} u;
	int matched;
};

struct smokey_test {
	const char *name;
	struct smokey_arg *args;
	int nargs;
	const char *description;
	int (*run)(struct smokey_test *t,
		   int argc, char *const argv[]);
	struct {
		int id;
		struct pvholder next;
	} __reserved;
};

#define for_each_smokey_test(__pos)	\
	pvlist_for_each_entry((__pos), &smokey_test_list, __reserved.next)

#define __smokey_arg_count(__args)	\
	ARRAY_SIZE(__args)

#define smokey_test_plugin(__plugin, __args, __desc)			\
	static int run_ ## __plugin(struct smokey_test *t,		\
				    int argc, char *const argv[]);	\
	static struct smokey_test __plugin = {				\
		.name = #__plugin,					\
		.args = (__args),					\
		.nargs = __smokey_arg_count(__args),			\
		.description = (__desc),				\
		.run = run_ ## __plugin,				\
	};								\
	__early_ctor void smokey_plugin_ ## __plugin(void);		\
	void smokey_plugin_ ## __plugin(void)				\
	{								\
		smokey_register_plugin(&(__plugin));			\
	}

#define SMOKEY_ARG(__plugin, __arg)	   (smokey_lookup_arg(&(__plugin), # __arg))
#define SMOKEY_ARG_ISSET(__plugin, __arg)  (SMOKEY_ARG(__plugin, __arg)->matched)
#define SMOKEY_ARG_INT(__plugin, __arg)	   (SMOKEY_ARG(__plugin, __arg)->u.n_val)
#define SMOKEY_ARG_BOOL(__plugin, __arg)   (!!SMOKEY_ARG_INT(__plugin, __arg))
#define SMOKEY_ARG_STRING(__plugin, __arg) (SMOKEY_ARG(__plugin, __arg)->u.s_val)
#define SMOKEY_ARG_SIZE(__plugin, __arg)   (SMOKEY_ARG(__plugin, __arg)->u.l_val)

#define smokey_arg_isset(__t, __name)      (smokey_lookup_arg(__t, __name)->matched)
#define smokey_arg_int(__t, __name)	   (smokey_lookup_arg(__t, __name)->u.n_val)
#define smokey_arg_bool(__t, __name)       (!!smokey_arg_int(__t, __name))
#define smokey_arg_string(__t, __name)     (smokey_lookup_arg(__t, __name)->u.s_val)
#define smokey_arg_size(__t, __name)       (smokey_lookup_arg(__t, __name)->u.l_val)

#define smokey_check_errno(__expr)					\
	({                                                              \
		int __ret = (__expr);					\
		if (__ret < 0) {					\
			__ret = -errno;					\
			__smokey_warning(__FILE__, __LINE__, "%s: %s",	\
					 #__expr, strerror(errno));	\
		}							\
		__ret;							\
	})

#define smokey_check_status(__expr)					\
	({                                                              \
		int __ret = (__expr);					\
		if (__ret) {						\
			__smokey_warning(__FILE__, __LINE__, "%s: %s",	\
					 #__expr, strerror(__ret));	\
			__ret = -__ret;					\
		}							\
		__ret;							\
	})

#define smokey_assert(__expr)						\
	({                                                              \
		int __ret = (__expr);					\
		if (!__ret) 						\
			__smokey_warning(__FILE__, __LINE__,		\
					 "assertion failed: %s", #__expr); \
		__ret;							\
	})

#define smokey_warning(__fmt, __args...)	\
	__smokey_warning(__FILE__, __LINE__, __fmt, ##__args)

#define __T(__ret, __action)					\
	({							\
		(__ret) = (__action);				\
		if (__ret) {					\
			if ((__ret) > 0)			\
				(__ret) = -(__ret);		\
			smokey_warning("FAILED: %s (=%s)",	\
				       __stringify(__action),	\
				       symerror(__ret));	\
		}						\
		(__ret) == 0;					\
	})

#define __F(__ret, __action)					\
	({							\
		(__ret) = (__action);				\
		if ((__ret) == 0)				\
			smokey_warning("FAILED: %s (=0)",	\
				       __stringify(__action));	\
		else if ((__ret) > 0)				\
			(__ret) = -(__ret);			\
		(__ret) != 0;					\
	})

#define __Terrno(__ret, __action)				\
	({							\
		(__ret) = (__action);				\
		if (__ret) {					\
			(__ret) = -errno;			\
			smokey_warning("FAILED: %s (=%s)",	\
				       __stringify(__action),	\
				       symerror(__ret));	\
		}						\
		(__ret) == 0;					\
	})

#define __Tassert(__expr)					\
	({							\
		int __ret = !!(__expr);				\
		if (!__ret)					\
			smokey_warning("FAILED: %s (=false)",	\
				       __stringify(__expr));	\
		__ret;						\
	})

#define __Fassert(__expr)					\
	({							\
		int __ret = (__expr);				\
		if (__ret)					\
			smokey_warning("FAILED: %s (=true)",	\
				       __stringify(__expr));	\
		!__ret;						\
	})

struct smokey_barrier {
	pthread_mutex_t lock;
	pthread_cond_t barrier;
	int signaled;
};

#ifdef __cplusplus
extern "C" {
#endif

void smokey_register_plugin(struct smokey_test *t);

int smokey_int(const char *s, struct smokey_arg *arg);

int smokey_bool(const char *s, struct smokey_arg *arg);

int smokey_string(const char *s, struct smokey_arg *arg);

int smokey_size(const char *s, struct smokey_arg *arg);

struct smokey_arg *smokey_lookup_arg(struct smokey_test *t,
				     const char *arg);

int smokey_parse_args(struct smokey_test *t,
		      int argc, char *const argv[]);

void smokey_vatrace(const char *fmt, va_list ap);
  
void smokey_trace(const char *fmt, ...);

void smokey_note(const char *fmt, ...);

void __smokey_warning(const char *file, int lineno,
		      const char *fmt, ...);

int smokey_barrier_init(struct smokey_barrier *b);

void smokey_barrier_destroy(struct smokey_barrier *b);

int smokey_barrier_wait(struct smokey_barrier *b);

int smokey_barrier_timedwait(struct smokey_barrier *b,
			     struct timespec *ts);
  
void smokey_barrier_release(struct smokey_barrier *b);

int smokey_fork_exec(const char *path, const char *arg);

int smokey_modprobe(const char *name, bool silent);

int smokey_rmmod(const char *name);

int smokey_run_extprog(const char *dir, const char *name, const char *args,
		       int *test_ret);

#ifdef __cplusplus
}
#endif

extern struct pvlistobj smokey_test_list;

extern int smokey_keep_going;

extern int smokey_verbose_mode;

extern int smokey_on_vm;

#endif /* _XENOMAI_SMOKEY_SMOKEY_H */
