/* Spa
 *
 * Copyright © 2018 Wim Taymans
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#ifndef SPA_POD_PARSER_H
#define SPA_POD_PARSER_H

#ifdef __cplusplus
extern "C" {
#endif

#include <errno.h>
#include <stdarg.h>

#include <spa/pod/iter.h>
#include <spa/pod/vararg.h>

struct spa_pod_parser_state {
	uint32_t offset;
	uint32_t flags;
	struct spa_pod_frame *frame;
};

struct spa_pod_parser {
	const void *data;
	uint32_t size;
	uint32_t _padding;
	struct spa_pod_parser_state state;
};

#define SPA_POD_PARSER_INIT(buffer,size)  (struct spa_pod_parser){ buffer, size, 0, {} }

static inline void spa_pod_parser_init(struct spa_pod_parser *parser,
				       const void *data, uint32_t size)
{
	*parser = SPA_POD_PARSER_INIT(data, size);
}

static inline void spa_pod_parser_pod(struct spa_pod_parser *parser,
				      const struct spa_pod *pod)
{
	spa_pod_parser_init(parser, pod, SPA_POD_SIZE(pod));
}

static inline void
spa_pod_parser_get_state(struct spa_pod_parser *parser, struct spa_pod_parser_state *state)
{
	*state = parser->state;
}

static inline void
spa_pod_parser_reset(struct spa_pod_parser *parser, struct spa_pod_parser_state *state)
{
	parser->state = *state;
}

static inline struct spa_pod *
spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t size)
{
	if (offset + 8 <= size) {
		struct spa_pod *pod = SPA_PTROFF(parser->data, offset, struct spa_pod);
		if (offset + SPA_POD_SIZE(pod) <= size)
			return pod;
	}
        return NULL;
}

static inline struct spa_pod *spa_pod_parser_frame(struct spa_pod_parser *parser, struct spa_pod_frame *frame)
{
	return SPA_PTROFF(parser->data, frame->offset, struct spa_pod);
}

static inline void spa_pod_parser_push(struct spa_pod_parser *parser,
		      struct spa_pod_frame *frame, const struct spa_pod *pod, uint32_t offset)
{
	frame->pod = *pod;
	frame->offset = offset;
	frame->parent = parser->state.frame;
	frame->flags = parser->state.flags;
	parser->state.frame = frame;
}

static inline struct spa_pod *spa_pod_parser_current(struct spa_pod_parser *parser)
{
	struct spa_pod_frame *f = parser->state.frame;
	uint32_t size = f ? f->offset + SPA_POD_SIZE(&f->pod) : parser->size;
	return spa_pod_parser_deref(parser, parser->state.offset, size);
}

static inline void spa_pod_parser_advance(struct spa_pod_parser *parser, const struct spa_pod *pod)
{
	parser->state.offset += SPA_ROUND_UP_N(SPA_POD_SIZE(pod), 8);
}

static inline struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser)
{
	struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod)
		spa_pod_parser_advance(parser, pod);
	return pod;
}

static inline int spa_pod_parser_pop(struct spa_pod_parser *parser,
		      struct spa_pod_frame *frame)
{
	parser->state.frame = frame->parent;
	parser->state.offset = frame->offset + SPA_ROUND_UP_N(SPA_POD_SIZE(&frame->pod), 8);
	return 0;
}

static inline int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bool *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_bool(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint32_t *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_id(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_int(struct spa_pod_parser *parser, int32_t *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_int(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_long(struct spa_pod_parser *parser, int64_t *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_long(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_float(struct spa_pod_parser *parser, float *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_float(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_double(struct spa_pod_parser *parser, double *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_double(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_string(struct spa_pod_parser *parser, const char **value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_string(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, const void **value, uint32_t *len)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_bytes(pod, value, len)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, uint32_t *type, const void **value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_pointer(pod, type, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int64_t *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_fd(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_rectangle(struct spa_pod_parser *parser, struct spa_rectangle *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_rectangle(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_fraction(struct spa_pod_parser *parser, struct spa_fraction *value)
{
	int res = -EPIPE;
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod != NULL && (res = spa_pod_get_fraction(pod, value)) >= 0)
		spa_pod_parser_advance(parser, pod);
	return res;
}

static inline int spa_pod_parser_get_pod(struct spa_pod_parser *parser, struct spa_pod **value)
{
	struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod == NULL)
		return -EPIPE;
	*value = pod;
	spa_pod_parser_advance(parser, pod);
	return 0;
}
static inline int spa_pod_parser_push_struct(struct spa_pod_parser *parser,
		struct spa_pod_frame *frame)
{
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod == NULL)
		return -EPIPE;
	if (!spa_pod_is_struct(pod))
		return -EINVAL;
	spa_pod_parser_push(parser, frame, pod, parser->state.offset);
	parser->state.offset += sizeof(struct spa_pod_struct);
	return 0;
}

static inline int spa_pod_parser_push_object(struct spa_pod_parser *parser,
		struct spa_pod_frame *frame, uint32_t type, uint32_t *id)
{
	const struct spa_pod *pod = spa_pod_parser_current(parser);
	if (pod == NULL)
		return -EPIPE;
	if (!spa_pod_is_object(pod))
		return -EINVAL;
	if (type != SPA_POD_OBJECT_TYPE(pod))
		return -EPROTO;
	if (id != NULL)
		*id = SPA_POD_OBJECT_ID(pod);
	spa_pod_parser_push(parser, frame, pod, parser->state.offset);
	parser->state.offset = parser->size;
	return 0;
}

static inline bool spa_pod_parser_can_collect(const struct spa_pod *pod, char type)
{
	if (pod == NULL)
		return false;

	if (spa_pod_is_choice(pod) &&
	    SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None &&
	    spa_pod_parser_can_collect(SPA_POD_CHOICE_CHILD(pod), type))
		return true;

	switch (type) {
	case 'P':
		return true;
	case 'b':
		return spa_pod_is_bool(pod);
	case 'I':
		return spa_pod_is_id(pod);
	case 'i':
		return spa_pod_is_int(pod);
	case 'l':
		return spa_pod_is_long(pod);
	case 'f':
		return spa_pod_is_float(pod);
	case 'd':
		return spa_pod_is_double(pod);
	case 's':
		return spa_pod_is_string(pod) || spa_pod_is_none(pod);
	case 'S':
		return spa_pod_is_string(pod);
	case 'y':
		return spa_pod_is_bytes(pod);
	case 'R':
		return spa_pod_is_rectangle(pod);
	case 'F':
		return spa_pod_is_fraction(pod);
	case 'B':
		return spa_pod_is_bitmap(pod);
	case 'a':
		return spa_pod_is_array(pod);
	case 'p':
		return spa_pod_is_pointer(pod);
	case 'h':
		return spa_pod_is_fd(pod);
	case 'T':
		return spa_pod_is_struct(pod) || spa_pod_is_none(pod);
	case 'O':
		return spa_pod_is_object(pod) || spa_pod_is_none(pod);
	case 'V':
		return spa_pod_is_choice(pod);
	default:
		return false;
	}
}

#define SPA_POD_PARSER_COLLECT(pod,_type,args)						\
do {											\
	switch (_type) {								\
	case 'b':									\
		*va_arg(args, bool*) = SPA_POD_VALUE(struct spa_pod_bool, pod);		\
		break;									\
	case 'I':									\
	case 'i':									\
		*va_arg(args, int32_t*) = SPA_POD_VALUE(struct spa_pod_int, pod);	\
		break;									\
	case 'l':									\
		*va_arg(args, int64_t*) = SPA_POD_VALUE(struct spa_pod_long, pod);	\
		break;									\
	case 'f':									\
		*va_arg(args, float*) = SPA_POD_VALUE(struct spa_pod_float, pod);	\
		break;									\
	case 'd':									\
		*va_arg(args, double*) = SPA_POD_VALUE(struct spa_pod_double, pod);	\
		break;									\
	case 's':									\
		*va_arg(args, char**) =							\
			(pod == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None)		\
				? NULL							\
				: (char *)SPA_POD_CONTENTS(struct spa_pod_string, pod));	\
		break;									\
	case 'S':									\
	{										\
		char *dest = va_arg(args, char*);					\
		uint32_t maxlen = va_arg(args, uint32_t);				\
		strncpy(dest, (char *)SPA_POD_CONTENTS(struct spa_pod_string, pod), maxlen-1);	\
		break;									\
	}										\
	case 'y':									\
		*(va_arg(args, void **)) = SPA_POD_CONTENTS(struct spa_pod_bytes, pod);	\
		*(va_arg(args, uint32_t *)) = SPA_POD_BODY_SIZE(pod);			\
		break;									\
	case 'R':									\
		*va_arg(args, struct spa_rectangle*) =					\
				SPA_POD_VALUE(struct spa_pod_rectangle, pod);		\
		break;									\
	case 'F':									\
		*va_arg(args, struct spa_fraction*) =					\
				SPA_POD_VALUE(struct spa_pod_fraction, pod);		\
		break;									\
	case 'B':									\
		*va_arg(args, uint32_t **) =						\
			(uint32_t *) SPA_POD_CONTENTS(struct spa_pod_bitmap, pod);	\
		break;									\
	case 'a':									\
		*va_arg(args, uint32_t*) = SPA_POD_ARRAY_VALUE_SIZE(pod);		\
		*va_arg(args, uint32_t*) = SPA_POD_ARRAY_VALUE_TYPE(pod);		\
		*va_arg(args, uint32_t*) = SPA_POD_ARRAY_N_VALUES(pod);			\
		*va_arg(args, void**) = SPA_POD_ARRAY_VALUES(pod);			\
		break;									\
	case 'p':									\
	{										\
		struct spa_pod_pointer_body *b =					\
				(struct spa_pod_pointer_body *) SPA_POD_BODY(pod);	\
		*(va_arg(args, uint32_t *)) = b->type;					\
		*(va_arg(args, const void **)) = b->value;				\
		break;									\
	}										\
	case 'h':									\
		*va_arg(args, int64_t*) = SPA_POD_VALUE(struct spa_pod_fd, pod);	\
		break;									\
	case 'P':									\
	case 'T':									\
	case 'O':									\
	case 'V':									\
	{										\
		const struct spa_pod **d = va_arg(args, const struct spa_pod**);	\
		if (d)									\
			*d = (pod == NULL || (SPA_POD_TYPE(pod) == SPA_TYPE_None)	\
				? NULL : pod);						\
		break;									\
	}										\
	default:									\
		break;									\
	}										\
} while(false)

#define SPA_POD_PARSER_SKIP(_type,args)							\
do {											\
	switch (_type) {								\
	case 'S':									\
		va_arg(args, char*);							\
		va_arg(args, uint32_t);							\
		break;									\
	case 'a':									\
		va_arg(args, void*);							\
		va_arg(args, void*);							\
		SPA_FALLTHROUGH 							\
	case 'p':									\
	case 'y':									\
		va_arg(args, void*);							\
		SPA_FALLTHROUGH 							\
	case 'b':									\
	case 'I':									\
	case 'i':									\
	case 'l':									\
	case 'f':									\
	case 'd':									\
	case 's':									\
	case 'R':									\
	case 'F':									\
	case 'B':									\
	case 'h':									\
	case 'V':									\
	case 'P':									\
	case 'T':									\
	case 'O':									\
		va_arg(args, void*);							\
		break;									\
	}										\
} while(false)

static inline int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list args)
{
	struct spa_pod_frame *f = parser->state.frame;
        uint32_t ftype = f ? f->pod.type : (uint32_t)SPA_TYPE_Struct;
	const struct spa_pod_prop *prop = NULL;
	int count = 0;

	do {
		bool optional;
		const struct spa_pod *pod = NULL;
		const char *format;

		if (ftype == SPA_TYPE_Object) {
			uint32_t key = va_arg(args, uint32_t);
			const struct spa_pod_object *object;

			if (key == 0)
				break;

			object = (const struct spa_pod_object *)spa_pod_parser_frame(parser, f);
			prop = spa_pod_object_find_prop(object, prop, key);
			pod = prop ? &prop->value : NULL;
		}

		if ((format = va_arg(args, char *)) == NULL)
			break;

		if (ftype == SPA_TYPE_Struct)
			pod = spa_pod_parser_next(parser);

		if ((optional = (*format == '?')))
			format++;

		if (!spa_pod_parser_can_collect(pod, *format)) {
			if (!optional) {
				if (pod == NULL)
					return -ESRCH;
				else
					return -EPROTO;
			}
			SPA_POD_PARSER_SKIP(*format, args);
		} else {
			if (pod->type == SPA_TYPE_Choice && *format != 'V' &&
			    SPA_POD_CHOICE_TYPE(pod) == SPA_CHOICE_None)
				pod = SPA_POD_CHOICE_CHILD(pod);

			SPA_POD_PARSER_COLLECT(pod, *format, args);
			count++;
		}
	} while (true);

	return count;
}

static inline int spa_pod_parser_get(struct spa_pod_parser *parser, ...)
{
	int res;
	va_list args;

	va_start(args, parser);
	res = spa_pod_parser_getv(parser, args);
	va_end(args);

	return res;
}

#define SPA_POD_OPT_Bool(val)				"?" SPA_POD_Bool(val)
#define SPA_POD_OPT_Id(val)				"?" SPA_POD_Id(val)
#define SPA_POD_OPT_Int(val)				"?" SPA_POD_Int(val)
#define SPA_POD_OPT_Long(val)				"?" SPA_POD_Long(val)
#define SPA_POD_OPT_Float(val)				"?" SPA_POD_Float(val)
#define SPA_POD_OPT_Double(val)				"?" SPA_POD_Double(val)
#define SPA_POD_OPT_String(val)				"?" SPA_POD_String(val)
#define SPA_POD_OPT_Stringn(val,len)			"?" SPA_POD_Stringn(val,len)
#define SPA_POD_OPT_Bytes(val,len)			"?" SPA_POD_Bytes(val,len)
#define SPA_POD_OPT_Rectangle(val)			"?" SPA_POD_Rectangle(val)
#define SPA_POD_OPT_Fraction(val)			"?" SPA_POD_Fraction(val)
#define SPA_POD_OPT_Array(csize,ctype,n_vals,vals)	"?" SPA_POD_Array(csize,ctype,n_vals,vals)
#define SPA_POD_OPT_Pointer(type,val)			"?" SPA_POD_Pointer(type,val)
#define SPA_POD_OPT_Fd(val)				"?" SPA_POD_Fd(val)
#define SPA_POD_OPT_Pod(val)				"?" SPA_POD_Pod(val)
#define SPA_POD_OPT_PodObject(val)			"?" SPA_POD_PodObject(val)
#define SPA_POD_OPT_PodStruct(val)			"?" SPA_POD_PodStruct(val)
#define SPA_POD_OPT_PodChoice(val)			"?" SPA_POD_PodChoice(val)

#define spa_pod_parser_get_object(p,type,id,...)				\
({										\
	struct spa_pod_frame _f;						\
	int _res;								\
	if ((_res = spa_pod_parser_push_object(p, &_f, type, id)) == 0) {	\
		_res = spa_pod_parser_get(p,##__VA_ARGS__, 0);			\
		spa_pod_parser_pop(p, &_f);					\
	}									\
	_res;									\
})

#define spa_pod_parser_get_struct(p,...)				\
({									\
	struct spa_pod_frame _f;					\
	int _res;							\
	if ((_res = spa_pod_parser_push_struct(p, &_f)) == 0) {		\
		_res = spa_pod_parser_get(p,##__VA_ARGS__, NULL);	\
		spa_pod_parser_pop(p, &_f);				\
	}								\
	_res;							\
})

#define spa_pod_parse_object(pod,type,id,...)			\
({								\
	struct spa_pod_parser _p;				\
	spa_pod_parser_pod(&_p, pod);				\
	spa_pod_parser_get_object(&_p,type,id,##__VA_ARGS__);	\
})

#define spa_pod_parse_struct(pod,...)				\
({								\
	struct spa_pod_parser _p;				\
	spa_pod_parser_pod(&_p, pod);				\
	spa_pod_parser_get_struct(&_p,##__VA_ARGS__);		\
})

#ifdef __cplusplus
}  /* extern "C" */
#endif

#endif /* SPA_POD_PARSER_H */
