/* Copyright 2016 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "cras_observer.h"

#include "cras_alert.h"
#include "cras_iodev_list.h"
#include "utlist.h"

struct cras_observer_client {
	struct cras_observer_ops ops;
	void *context;
	struct cras_observer_client *next, *prev;
};

struct cras_observer_alerts {
	struct cras_alert *output_volume;
	struct cras_alert *output_mute;
	struct cras_alert *capture_gain;
	struct cras_alert *capture_mute;
	struct cras_alert *nodes;
	struct cras_alert *active_node;
	struct cras_alert *output_node_volume;
	struct cras_alert *node_left_right_swapped;
	struct cras_alert *input_node_gain;
	struct cras_alert *suspend_changed;
	/* If all events for active streams went through a single alert then
         * we might miss some because the alert code does not send every
         * alert message. To ensure that the event sent contains the correct
         * number of active streams per direction, make the alerts
         * per-direciton. */
	struct cras_alert *num_active_streams[CRAS_NUM_DIRECTIONS];
};

struct cras_observer_server {
	struct cras_observer_alerts alerts;
	struct cras_observer_client *clients;
};

struct cras_observer_alert_data_volume {
	int32_t volume;
};

struct cras_observer_alert_data_mute {
	int muted;
	int user_muted;
	int mute_locked;
};

struct cras_observer_alert_data_active_node {
	enum CRAS_STREAM_DIRECTION direction;
	cras_node_id_t node_id;
};

struct cras_observer_alert_data_node_volume {
	cras_node_id_t node_id;
	int32_t volume;
};

struct cras_observer_alert_data_node_lr_swapped {
	cras_node_id_t node_id;
	int swapped;
};

struct cras_observer_alert_data_suspend {
	int suspended;
};

struct cras_observer_alert_data_streams {
	enum CRAS_STREAM_DIRECTION direction;
	uint32_t num_active_streams;
};

/* Global observer instance. */
static struct cras_observer_server *g_observer;

/* Empty observer ops. */
static struct cras_observer_ops g_empty_ops;

/*
 * Alert handlers for delayed callbacks.
 */

static void output_volume_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_volume *volume_data =
		(struct cras_observer_alert_data_volume *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.output_volume_changed)
			client->ops.output_volume_changed(
					client->context,
					volume_data->volume);
	}
}

static void output_mute_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_mute *mute_data =
		(struct cras_observer_alert_data_mute *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.output_mute_changed)
			client->ops.output_mute_changed(
					client->context,
					mute_data->muted,
					mute_data->user_muted,
					mute_data->mute_locked);
	}
}

static void capture_gain_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_volume *volume_data =
		(struct cras_observer_alert_data_volume *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.capture_gain_changed)
			client->ops.capture_gain_changed(
					client->context,
					volume_data->volume);
	}
}

static void capture_mute_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_mute *mute_data =
		(struct cras_observer_alert_data_mute *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.capture_mute_changed)
			client->ops.capture_mute_changed(
					client->context,
					mute_data->muted,
					mute_data->mute_locked);
	}
}

static void nodes_prepare(struct cras_alert *alert)
{
	cras_iodev_list_update_device_list();
}

static void nodes_alert(void *arg, void *data)
{
	struct cras_observer_client *client;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.nodes_changed)
			client->ops.nodes_changed(client->context);
	}
}

static void active_node_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_active_node *node_data =
		(struct cras_observer_alert_data_active_node *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.active_node_changed)
			client->ops.active_node_changed(
					client->context,
					node_data->direction,
					node_data->node_id);
	}
}

static void output_node_volume_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_node_volume *node_data =
		(struct cras_observer_alert_data_node_volume *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.output_node_volume_changed)
			client->ops.output_node_volume_changed(
					client->context,
					node_data->node_id,
					node_data->volume);
	}
}

static void node_left_right_swapped_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_node_lr_swapped *node_data =
		(struct cras_observer_alert_data_node_lr_swapped *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.node_left_right_swapped_changed)
			client->ops.node_left_right_swapped_changed(
					client->context,
					node_data->node_id,
					node_data->swapped);
	}
}

static void input_node_gain_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_node_volume *node_data =
		(struct cras_observer_alert_data_node_volume *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.input_node_gain_changed)
			client->ops.input_node_gain_changed(
					client->context,
					node_data->node_id,
					node_data->volume);
	}
}

static void suspend_changed_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_suspend *suspend_data =
		(struct cras_observer_alert_data_suspend *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.suspend_changed)
			client->ops.suspend_changed(
					client->context,
					suspend_data->suspended);
	}
}

static void num_active_streams_alert(void *arg, void *data)
{
	struct cras_observer_client *client;
	struct cras_observer_alert_data_streams *streams_data =
		(struct cras_observer_alert_data_streams *)data;

	DL_FOREACH(g_observer->clients, client) {
		if (client->ops.num_active_streams_changed)
			client->ops.num_active_streams_changed(
					client->context,
					streams_data->direction,
					streams_data->num_active_streams);
	}
}

static int cras_observer_server_set_alert(struct cras_alert **alert,
					  cras_alert_cb cb,
					  cras_alert_prepare prepare,
					  unsigned int flags)
{
	*alert = cras_alert_create(prepare, flags);
	if (!*alert)
		return -ENOMEM;
	return cras_alert_add_callback(*alert, cb, NULL);
}

#define CRAS_OBSERVER_SET_ALERT(alert,prepare,flags) \
	do { \
		rc = cras_observer_server_set_alert( \
			&g_observer->alerts.alert, alert##_alert, \
			prepare, flags); \
		if (rc) \
			goto error; \
	} while(0)

#define CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(alert,direction) \
	do { \
		rc = cras_observer_server_set_alert( \
			&g_observer->alerts.alert[direction], \
			alert##_alert, NULL, 0); \
		if (rc) \
			goto error; \
	} while(0)

/*
 * Public interface
 */

int cras_observer_server_init()
{
	int rc;

	memset(&g_empty_ops, 0, sizeof(g_empty_ops));
	g_observer = (struct cras_observer_server *)
			calloc(1, sizeof(struct cras_observer_server));
	if (!g_observer)
		return -ENOMEM;

	CRAS_OBSERVER_SET_ALERT(output_volume, NULL, 0);
	CRAS_OBSERVER_SET_ALERT(output_mute, NULL, 0);
	CRAS_OBSERVER_SET_ALERT(capture_gain, NULL, 0);
	CRAS_OBSERVER_SET_ALERT(capture_mute, NULL, 0);
	CRAS_OBSERVER_SET_ALERT(nodes, nodes_prepare, 0);
	CRAS_OBSERVER_SET_ALERT(active_node, nodes_prepare,
				CRAS_ALERT_FLAG_KEEP_ALL_DATA);
	CRAS_OBSERVER_SET_ALERT(output_node_volume, NULL, 0);
	CRAS_OBSERVER_SET_ALERT(node_left_right_swapped, NULL, 0);
	CRAS_OBSERVER_SET_ALERT(input_node_gain, NULL, 0);
	CRAS_OBSERVER_SET_ALERT(suspend_changed, NULL, 0);

	CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(
		num_active_streams, CRAS_STREAM_OUTPUT);
	CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(
		num_active_streams, CRAS_STREAM_INPUT);
	CRAS_OBSERVER_SET_ALERT_WITH_DIRECTION(
		num_active_streams, CRAS_STREAM_POST_MIX_PRE_DSP);
	return 0;

error:
	cras_observer_server_free();
	return rc;
}

void cras_observer_server_free()
{
	if (!g_observer)
		return;
	cras_alert_destroy(g_observer->alerts.output_volume);
	cras_alert_destroy(g_observer->alerts.output_mute);
	cras_alert_destroy(g_observer->alerts.capture_gain);
	cras_alert_destroy(g_observer->alerts.capture_mute);
	cras_alert_destroy(g_observer->alerts.nodes);
	cras_alert_destroy(g_observer->alerts.active_node);
	cras_alert_destroy(g_observer->alerts.output_node_volume);
	cras_alert_destroy(g_observer->alerts.node_left_right_swapped);
	cras_alert_destroy(g_observer->alerts.input_node_gain);
	cras_alert_destroy(g_observer->alerts.suspend_changed);
	cras_alert_destroy(g_observer->alerts.num_active_streams[
							CRAS_STREAM_OUTPUT]);
	cras_alert_destroy(g_observer->alerts.num_active_streams[
							CRAS_STREAM_INPUT]);
	cras_alert_destroy(g_observer->alerts.num_active_streams[
						CRAS_STREAM_POST_MIX_PRE_DSP]);
	free(g_observer);
	g_observer = NULL;
}

int cras_observer_ops_are_empty(const struct cras_observer_ops *ops)
{
	return memcmp(ops, &g_empty_ops, sizeof(*ops)) == 0;
}

void cras_observer_get_ops(const struct cras_observer_client *client,
			   struct cras_observer_ops *ops)
{
	if (!ops)
		return;
	if (!client)
		memset(ops, 0, sizeof(*ops));
	else
		memcpy(ops, &client->ops, sizeof(*ops));
}

void cras_observer_set_ops(struct cras_observer_client *client,
			   const struct cras_observer_ops *ops)
{
	if (!client)
		return;
	if (!ops)
		memset(&client->ops, 0, sizeof(client->ops));
	else
		memcpy(&client->ops, ops, sizeof(client->ops));
}

struct cras_observer_client *cras_observer_add(
			const struct cras_observer_ops *ops,
			void *context)
{
	struct cras_observer_client *client;

	client = (struct cras_observer_client *)calloc(1, sizeof(*client));
	if (!client)
		return NULL;
	client->context = context;
	DL_APPEND(g_observer->clients, client);
	cras_observer_set_ops(client, ops);
	return client;
}

void cras_observer_remove(struct cras_observer_client *client)
{
	if (!client)
		return;
	DL_DELETE(g_observer->clients, client);
	free(client);
}

/*
 * Public interface for notifiers.
 */

void cras_observer_notify_output_volume(int32_t volume)
{
	struct cras_observer_alert_data_volume data;

	data.volume = volume;
	cras_alert_pending_data(g_observer->alerts.output_volume,
				&data, sizeof(data));
}

void cras_observer_notify_output_mute(int muted, int user_muted,
				      int mute_locked)
{
	struct cras_observer_alert_data_mute data;

	data.muted = muted;
	data.user_muted = user_muted;
	data.mute_locked = mute_locked;
	cras_alert_pending_data(g_observer->alerts.output_mute,
				&data, sizeof(data));
}

void cras_observer_notify_capture_gain(int32_t gain)
{
	struct cras_observer_alert_data_volume data;

	data.volume = gain;
	cras_alert_pending_data(g_observer->alerts.capture_gain,
				&data, sizeof(data));
}

void cras_observer_notify_capture_mute(int muted, int mute_locked)
{
	struct cras_observer_alert_data_mute data;

	data.muted = muted;
	data.user_muted = 0;
	data.mute_locked = mute_locked;
	cras_alert_pending_data(g_observer->alerts.capture_mute,
				&data, sizeof(data));
}

void cras_observer_notify_nodes(void)
{
	cras_alert_pending(g_observer->alerts.nodes);
}

void cras_observer_notify_active_node(enum CRAS_STREAM_DIRECTION dir,
				      cras_node_id_t node_id)
{
	struct cras_observer_alert_data_active_node data;

	data.direction = dir;
	data.node_id = node_id;
	cras_alert_pending_data(g_observer->alerts.active_node,
				&data, sizeof(data));
}

void cras_observer_notify_output_node_volume(cras_node_id_t node_id,
					     int32_t volume)
{
	struct cras_observer_alert_data_node_volume data;

	data.node_id = node_id;
	data.volume = volume;
	cras_alert_pending_data(g_observer->alerts.output_node_volume,
				&data, sizeof(data));
}

void cras_observer_notify_node_left_right_swapped(cras_node_id_t node_id,
						  int swapped)
{
	struct cras_observer_alert_data_node_lr_swapped data;

	data.node_id = node_id;
	data.swapped = swapped;
	cras_alert_pending_data(g_observer->alerts.node_left_right_swapped,
				&data, sizeof(data));
}

void cras_observer_notify_input_node_gain(cras_node_id_t node_id,
					  int32_t gain)
{
	struct cras_observer_alert_data_node_volume data;

	data.node_id = node_id;
	data.volume = gain;
	cras_alert_pending_data(g_observer->alerts.input_node_gain,
				&data, sizeof(data));
}

void cras_observer_notify_suspend_changed(int suspended)
{
	struct cras_observer_alert_data_suspend data;

	data.suspended = suspended;
	cras_alert_pending_data(g_observer->alerts.suspend_changed,
				&data, sizeof(data));
}

void cras_observer_notify_num_active_streams(enum CRAS_STREAM_DIRECTION dir,
					     uint32_t num_active_streams)
{
	struct cras_observer_alert_data_streams data;
	struct cras_alert *alert;

	data.direction = dir;
	data.num_active_streams = num_active_streams;
	alert = g_observer->alerts.num_active_streams[dir];
	if (!alert)
		return;

	cras_alert_pending_data(alert, &data, sizeof(data));
}