/* PipeWire
 * Copyright (C) 2018 Wim Taymans <wim.taymans@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef __PIPEWIRE_PULSEAUDIO_INTERNAL_H__
#define __PIPEWIRE_PULSEAUDIO_INTERNAL_H__

#include <string.h>

#include <spa/utils/defs.h>
#include <spa/utils/hook.h>
#include <spa/utils/ringbuffer.h>
#include <spa/param/audio/format-utils.h>

#include <pulse/mainloop.h>
#include <pulse/stream.h>
#include <pulse/format.h>
#include <pulse/subscribe.h>
#include <pulse/introspect.h>
#include <pulse/version.h>

#include <pipewire/pipewire.h>

/* Some PulseAudio API added const qualifiers in 13.0 */
#if PA_MAJOR >= 13
#define PA_CONST const
#else
#define PA_CONST
#endif

#define PA_MAX_FORMATS (PA_ENCODING_MAX)

#ifdef __cplusplus
extern "C" {
#endif

#define pa_streq(a,b)		(!strcmp((a),(b)))
#define pa_strneq(a,b,n)	(!strncmp((a),(b),(n)))

#ifndef PA_LIKELY
#define PA_UNLIKELY		SPA_UNLIKELY
#define PA_LIKELY		SPA_LIKELY
#endif
#define PA_MIN			SPA_MIN
#define PA_MAX			SPA_MAX
#define pa_assert		spa_assert
#define pa_assert_se		spa_assert_se
#define pa_return_val_if_fail(expr, val)				\
	do {								\
		if (SPA_UNLIKELY(!(expr))) {				\
			pa_log_debug("Assertion '%s' failed at %s:%u %s()\n",	\
				#expr , __FILE__, __LINE__, __func__);	\
			return (val);					\
		}							\
	} while(false)

#define pa_assert_not_reached	spa_assert_not_reached

#define PA_INT_TYPE_SIGNED(type) (!!((type) 0 > (type) -1))

#define PA_INT_TYPE_HALF(type) ((type) 1 << (sizeof(type)*8 - 2))

#define PA_INT_TYPE_MAX(type)                                          \
    ((type) (PA_INT_TYPE_SIGNED(type)                                  \
             ? (PA_INT_TYPE_HALF(type) - 1 + PA_INT_TYPE_HALF(type))   \
             : (type) -1))

#define PA_INT_TYPE_MIN(type)                                          \
    ((type) (PA_INT_TYPE_SIGNED(type)                                  \
             ? (-1 - PA_INT_TYPE_MAX(type))                            \
             : (type) 0))


#ifdef __GNUC__
#define PA_CLAMP_UNLIKELY(x, low, high)                                 \
	__extension__ ({                                                    \
		typeof(x) _x = (x);                                         \
		typeof(low) _low = (low);                                   \
		typeof(high) _high = (high);                                \
		(PA_UNLIKELY(_x > _high) ? _high : (PA_UNLIKELY(_x < _low) ? _low : _x)); \
	})
#else
#define PA_CLAMP_UNLIKELY(x, low, high) (PA_UNLIKELY((x) > (high)) ? (high) : (PA_UNLIKELY((x) < (low)) ? (low) : (x)))
#endif

#ifdef __GNUC__
#define PA_ROUND_DOWN(a, b)				\
	__extension__ ({				\
		typeof(a) _a = (a);			\
		typeof(b) _b = (b);			\
		(_a / _b) * _b;				\
	})
#else
#define PA_ROUND_DOWN(a, b) (((a) / (b)) * (b))
#endif


#define pa_init_i18n()
#define _(String)		(String)
#define N_(String)		(String)

#define pa_snprintf		snprintf
#define pa_strip(n)		pw_strip(n,"\n\r \t")

#define pa_log			pw_log_info
#define pa_log_debug		pw_log_debug
#define pa_log_warn		pw_log_warn

static inline void* PA_ALIGN_PTR(const void *p) {
    return (void*) (((size_t) p) & ~(sizeof(void*) - 1));
}

/* Rounds up */
static inline size_t PA_ALIGN(size_t l) {
    return ((l + sizeof(void*) - 1) & ~(sizeof(void*) - 1));
}

static inline const char *pa_strnull(const char *x) {
    return x ? x : "(null)";
}

int pa_context_set_error(PA_CONST pa_context *c, int error);
void pa_context_fail(PA_CONST pa_context *c, int error);

#define PA_CHECK_VALIDITY(context, expression, error)			\
do {									\
	if (!(expression)) {						\
		pw_log_debug("'%s' failed at %s:%u %s()",		\
			#expression, __FILE__, __LINE__, __func__);	\
		return -pa_context_set_error((context), (error));	\
	}								\
} while(false)

#define PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, value)	\
do {									\
	if (!(expression)) {						\
		pw_log_debug("'%s' failed at %s:%u %s()",		\
			#expression, __FILE__, __LINE__, __func__);	\
		pa_context_set_error((context), (error));		\
		return value;						\
	}								\
} while(false)

#define PA_CHECK_VALIDITY_RETURN_NULL(context, expression, error)       \
    PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, NULL)

#define PA_FAIL(context, error)                                 \
    do {                                                        \
	pw_log_debug("fail at %s:%u %s()",			\
			, __FILE__, __LINE__, __func__);	\
        return -pa_context_set_error((context), (error));	\
    } while(false)

#define PA_FAIL_RETURN_ANY(context, error, value)      \
    do {                                               \
	pw_log_debug("fail at %s:%u %s()",			\
			, __FILE__, __LINE__, __func__);	\
        pa_context_set_error((context), (error));         \
        return value;                                  \
    } while(false)

#define PA_FAIL_RETURN_NULL(context, error)     \
    PA_FAIL_RETURN_ANY(context, error, NULL)


pa_proplist* pa_proplist_new_props(struct pw_properties *props);
pa_proplist* pa_proplist_new_dict(struct spa_dict *dict);
int pa_proplist_update_dict(pa_proplist *p, struct spa_dict *dict);
int pw_properties_update_proplist(struct pw_properties *props, PA_CONST pa_proplist *p);

struct pa_io_event {
	struct spa_source *source;
	struct pa_mainloop *mainloop;
	int fd;
	pa_io_event_flags_t events;
	pa_io_event_cb_t cb;
	void *userdata;
	pa_io_event_destroy_cb_t destroy;
};

struct pa_time_event {
	struct spa_source *source;
	struct pa_mainloop *mainloop;
	pa_time_event_cb_t cb;
	void *userdata;
	pa_time_event_destroy_cb_t destroy;
};

struct pa_defer_event {
	struct spa_source *source;
	struct pa_mainloop *mainloop;
	pa_defer_event_cb_t cb;
	void *userdata;
	pa_defer_event_destroy_cb_t destroy;
};

struct pa_mainloop {
	struct pw_loop *loop;
	struct spa_source *event;

	pa_mainloop_api api;

	bool quit;
	int retval;

	int timeout;
	int n_events;

	int fd;
	pa_poll_func poll_func;
	void *poll_func_userdata;
};

struct param {
	struct spa_list link;
	uint32_t id;
	void *param;
};

#define PA_IDX_FLAG_MONITOR		0x800000U
#define PA_IDX_MASK_MONITOR		0x7fffffU

struct port_device {
	uint32_t n_devices;
	uint32_t *devices;
};

struct global;

struct global_info {
	uint32_t version;
	const void *events;
	pw_destroy_t destroy;
	void (*sync) (struct global *g);
};

struct global {
	struct spa_list link;
	uint32_t id;
	uint32_t permissions;
	char *type;
	struct pw_properties *props;

	pa_context *context;
	pa_subscription_mask_t mask;
	pa_subscription_event_type_t event;

	int priority_driver;
	int init:1;
	int sync:1;

	int changed;
	void *info;
	struct global_info *ginfo;

	struct pw_proxy *proxy;
	struct spa_hook proxy_listener;
        struct spa_hook object_listener;

	pa_stream *stream;

	union {
		/* for links */
		struct {
			struct global *src;
			struct global *dst;
		} link_info;
		/* for sink/source */
		struct {
			uint32_t client_id;	/* if of owner client */
			uint32_t monitor;
#define NODE_FLAG_HW_VOLUME	(1 << 0)
#define NODE_FLAG_DEVICE_VOLUME	(1 << 1)
#define NODE_FLAG_HW_MUTE	(1 << 4)
#define NODE_FLAG_DEVICE_MUTE	(1 << 5)
			uint32_t flags;
			float volume;
			bool mute;
			pa_sample_spec sample_spec;
			pa_channel_map channel_map;
			uint32_t n_channel_volumes;
			float channel_volumes[SPA_AUDIO_MAX_CHANNELS];
			uint32_t device_id;		/* id of device (card) */
			uint32_t profile_device_id;	/* id in profile */
			float base_volume;
			float volume_step;
			uint32_t active_port;
			enum spa_param_availability available_port;
			uint32_t device_index;
			struct pw_array formats;
		} node_info;
		struct {
			uint32_t node_id;
		} port_info;
		/* for devices */
		struct {
			struct spa_list profiles;
			uint32_t n_profiles;
			uint32_t active_profile;
			struct spa_list ports;
			uint32_t n_ports;
			struct spa_list routes;
			uint32_t n_routes;
			pa_card_info info;
			pa_card_profile_info2 *card_profiles;
			unsigned int pending_profiles:1;
			pa_card_port_info *card_ports;
			unsigned int pending_ports:1;
			struct port_device *port_devices;
		} card_info;
		struct {
			pa_module_info info;
		} module_info;
		struct {
			pa_client_info info;
		} client_info;
	};
};

struct module_info {
	struct spa_list link;	/* link in context modules */
	uint32_t id;
	struct pw_proxy *proxy;
	struct spa_hook listener;
};

struct pa_context {
	int refcount;
	uint32_t client_index;

	pa_io_event *io;
	bool fallback_loop;
	struct pw_loop *loop;
	struct pw_context *context;

	struct pw_properties *props;

	struct pw_core *core;
	struct spa_hook core_listener;
	struct pw_core_info *core_info;

        struct pw_registry *registry;
        struct spa_hook registry_listener;

	pa_proplist *proplist;
	pa_mainloop_api *mainloop;

	int error;
	pa_context_state_t state;

	pa_context_notify_cb_t state_callback;
	void *state_userdata;
	pa_context_event_cb_t event_callback;
	void *event_userdata;
	pa_context_subscribe_cb_t subscribe_callback;
	void *subscribe_userdata;
	pa_subscription_mask_t subscribe_mask;

	struct spa_list globals;

	struct spa_list streams;
	struct spa_list operations;
	struct spa_list modules;

	int no_fail:1;
	int disconnect:1;

	int pending_seq;

	struct global *metadata;
	uint32_t default_sink;
	uint32_t default_source;
};

pa_stream *pa_context_find_stream(pa_context *c, uint32_t idx);
struct global *pa_context_find_global(pa_context *c, uint32_t id);
const char *pa_context_find_global_name(pa_context *c, uint32_t id);
struct global *pa_context_find_global_by_name(pa_context *c, uint32_t mask, const char *name);
struct global *pa_context_find_linked(pa_context *c, uint32_t id);

struct pa_mem {
	struct spa_list link;
	void *data;
	size_t maxsize;
	size_t size;
	size_t offset;
	void *user_data;
};

struct pa_stream {
	struct spa_list link;
	int refcount;

	struct pw_stream *stream;
	struct spa_hook stream_listener;

	pa_context *context;
	pa_proplist *proplist;

	pa_stream_direction_t direction;
	pa_stream_state_t state;
	pa_stream_flags_t flags;
	bool disconnecting;

	pa_sample_spec sample_spec;
	pa_channel_map channel_map;
	uint8_t n_formats;
	pa_format_info *req_formats[PA_MAX_FORMATS];
	pa_format_info *format;

	uint32_t stream_index;
	struct global *global;

	pa_buffer_attr buffer_attr;

	uint32_t device_index;
	char *device_name;

	pa_timing_info timing_info;
	uint64_t ticks_base;
	size_t queued_bytes;

	uint32_t direct_on_input;

	unsigned int suspended:1;
	unsigned int corked:1;
	unsigned int timing_info_valid:1;
	unsigned int have_time:1;

	pa_stream_notify_cb_t state_callback;
	void *state_userdata;
	pa_stream_request_cb_t read_callback;
	void *read_userdata;
	pa_stream_request_cb_t write_callback;
	void *write_userdata;
	pa_stream_notify_cb_t overflow_callback;
	void *overflow_userdata;
	pa_stream_notify_cb_t underflow_callback;
	void *underflow_userdata;
	pa_stream_notify_cb_t latency_update_callback;
	void *latency_update_userdata;
	pa_stream_notify_cb_t moved_callback;
	void *moved_userdata;
	pa_stream_notify_cb_t suspended_callback;
	void *suspended_userdata;
	pa_stream_notify_cb_t started_callback;
	void *started_userdata;
	pa_stream_event_cb_t event_callback;
	void *event_userdata;
	pa_stream_notify_cb_t buffer_attr_callback;
	void *buffer_attr_userdata;

	size_t maxsize;
	size_t maxblock;

	struct pa_mem *mem;	/* current mem for playback */
	struct spa_list free;	/* free to fill */
	struct spa_list ready;	/* ready for playback */
	size_t ready_bytes;

	struct pw_buffer *buffer;	/* currently reading for capture */

	uint32_t n_channel_volumes;
	float channel_volumes[SPA_AUDIO_MAX_CHANNELS];
	bool mute;
	pa_operation *drain;
};

void pa_stream_set_state(pa_stream *s, pa_stream_state_t st);

typedef void (*pa_operation_cb_t)(pa_operation *o, void *userdata);

struct pa_operation
{
	struct spa_list link;

	int refcount;
	pa_context *context;
	pa_stream *stream;
	unsigned int sync:1;

	pa_operation_state_t state;

	pa_operation_cb_t callback;
	void *userdata;

	pa_operation_notify_cb_t state_callback;
	void *state_userdata;
};

bool pa_mainloop_api_is_our_api(pa_mainloop_api *api);

#define PA_TIMEVAL_RTCLOCK ((time_t) (1LU << 30))
struct timeval* pa_rtclock_from_wallclock(struct timeval *tv);
struct timeval* pa_rtclock_to_wallclock(struct timeval *tv);
struct timeval* pa_timeval_rtstore(struct timeval *tv, pa_usec_t v, bool rtclock);

bool pa_endswith(const char *s, const char *sfx);

pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t cb, size_t userdata_size);
void pa_operation_done(pa_operation *o);
int pa_operation_sync(pa_operation *o);

#define METADATA_DEFAULT_SINK		"default.audio.sink"
#define METADATA_DEFAULT_SOURCE		"default.audio.source"
#define METADATA_TARGET_NODE		"target.node"

int pa_metadata_update(struct global *global, uint32_t subject, const char *key,
                        const char *type, const char *value);
int pa_metadata_get(struct global *global, uint32_t subject, const char *key,
                        const char **type, const char **value);

void pw_channel_map_from_positions(pa_channel_map *map, uint32_t n_pos, const uint32_t *pos);
void pw_channel_map_to_positions(const pa_channel_map *map, uint32_t *pos);

int pa_format_parse_param(const struct spa_pod *param,
		pa_sample_spec *spec, pa_channel_map *map);
const struct spa_pod *pa_format_build_param(struct spa_pod_builder *b,
		uint32_t id, pa_sample_spec *spec, pa_channel_map *map);

pa_format_info* pa_format_info_from_param(const struct spa_pod *param);

#ifdef __cplusplus
}
#endif

#endif /* __PIPEWIRE_PULSEAUDIO_INTERNAL_H__ */
