/* WirePlumber
 *
 * Copyright © 2023 Collabora Ltd.
 *    @author Ashok Sidipotu <ashok.sidipotu@collabora.com>
 *
 * SPDX-License-Identifier: MIT
 */

#include <wp/wp.h>
#include <stdio.h>
#include <getopt.h>
#include <math.h>
#include "audiomixer.h"

typedef struct _AudioMixerTest AudioMixerTest;

struct _AudioMixerTest
{
	struct audiomixer *am;
	WpCore *core;
	const struct mixer_control **ctrls;
	unsigned int nctrls;
	GMainLoop *loop;
};

static void
audio_mixer_clear (AudioMixerTest *self)
{
	audiomixer_free (self->am);
	g_clear_pointer (&self->loop, g_main_loop_unref);
}

static gint
set_mute (AudioMixerTest *self, gint id)
{
	gint ret = -1;
	gboolean mute = FALSE;
	int i;

	for (i = 0; i < self->nctrls; i++) {
		const struct mixer_control *ctrl = self->ctrls[i];
		if (id == i) {

			if (g_str_equal ("bass", ctrl->name) || g_str_equal ("treble", ctrl->name))
				g_warning ("mute cannot be applied for %s control", ctrl->name);
			else {
				/* toggle mute value */
				mute = (ctrl->mute) ? FALSE : TRUE;
				audiomixer_change_mute (self->am, ctrl, mute);
				ret = TRUE;
			}

			break;
		}
	}

	return ret;
}

static gint
set_gain (AudioMixerTest *self, gint id, gfloat gain)
{
	gint ret = -1;
	int i;

	for (i = 0; i < self->nctrls; i++) {
		const struct mixer_control *ctrl = self->ctrls[i];

		if(id == i) {

			if (g_str_equal ("bass", ctrl->name) || g_str_equal ("treble", ctrl->name)) {
				if (fabs (ctrl->gain - gain) < 0.000001)
					g_warning ("gain already at requested level %f", ctrl->gain);
				else {
					audiomixer_change_gain (self->am, ctrl, gain);
					ret = TRUE;
				}
			}
			else
				g_warning ("gain cannot be applied for %s control", ctrl->name);

			break;
		}
	}

	return ret;
}

static gint
set_volume (AudioMixerTest *self, gint id, double vol)
{
	gint ret = -1;
	int i;

	for (i = 0; i < self->nctrls; i++) {
		const struct mixer_control *ctrl = self->ctrls[i];

		if (id == i) {
			if (g_str_equal ("bass", ctrl->name) || g_str_equal ("treble", ctrl->name))
				g_warning ("volume cannot be applied for %s control", ctrl->name);
			else {

				if (fabs (ctrl->volume - vol) < 0.000001)
					g_warning ("volume is already at requested level %f", ctrl->volume);
				else {
					audiomixer_change_volume (self->am, ctrl, (double)vol);
					ret = TRUE;
				}

			}
			break;
		}
	}

	return ret;
}

static void
print_ctrls (AudioMixerTest *self)
{
	const struct mixer_control **ctrls = self->ctrls;
	unsigned int nctrls = self->nctrls;
	int i;

	fprintf (stdout, "\nControls:");
	for (i = 0; i < nctrls; i++) {
		const struct mixer_control *ctrl = ctrls[i];
		if (g_str_equal ("bass", ctrl->name) || g_str_equal ("treble", ctrl->name))
			fprintf(stdout, "\n%2d. %-25s   [gain: %.2f]", i, ctrl->name,
				ctrl->gain);
		else
			fprintf(stdout, "\n%2d. %-25s   [vol: %.2f, mute:%d]", i, ctrl->name,
				ctrl->volume, ctrl->mute);
	}
	fprintf (stdout, "\n");
}

static void
refresh_ctrls (AudioMixerTest *self)
{
	self->ctrls = audiomixer_get_active_controls (self->am, &self->nctrls);
	print_ctrls (self);
}

static void show_help (void)
{
	fprintf (stdout,
		"\n"
		"  -h, --help           Show this help\n"
		"  -p, --print-controls prints controls\n"
		"  -i, --id             control id(serial#) of the control, take a look at the controls to get the id of control\n"
		"                       Examples\n"
		"                       audio-mixer-test -> prints the controls and help text\n"
		"                       audio-mixer-test -p -> prints only the controls\n"
		"  -v, --set-volume     set volume level for volume controls(all controls except bass and treble)\n"
		"                       Examples\n"
		"                       audio-mixer-test -v 0.2       -> sets volume of the 1st control with 0.2\n"
		"                       audio-mixer-test -i 9 -v 0.2  -> sets volume of the 9th control with 0.2\n"
		"  -g, --set-gain       gain level for gain controls like bass and treble\n"
		"                       Examples\n"
		"                       audio-mixer-test -i 11 -g 0.8 -> sets gain of the 11th control with 0.8\n"
		"  -m, --set-mute       mute/unmute volume controls(all controls except bass and treble) takes no arguments\n"
		"                       Examples\n"
		"                       audio-mixer-test -m           -> mutes the 1st control\n"
		"                       audio-mixer-test -m           -> unmutes the 1st control, if it is issued after the above command\n"
		"                       audio-mixer-test -i 9 -m      -> mutes 9th control (Multimedia) with 0.8\n");
}

static void
mixer_value_change_cb (void *data,
	unsigned int change_mask,
	const struct mixer_control *ctrl)
{
	AudioMixerTest *self = (AudioMixerTest *)data;
	refresh_ctrls (self);
	g_main_loop_quit (self->loop);
}

static void
mixer_controls_changed (void *data)
{
	AudioMixerTest *self = (AudioMixerTest *)data;
	g_main_loop_quit (self->loop);
}

gint
main (gint argc, gchar **argv)
{
	AudioMixerTest self = { 0 };
	g_autoptr (GError) error = NULL;
	gint c, ret = 0;
	struct audiomixer_events audiomixer_events = { 0 };

	gint id = -1;
	double vol = 0.0;
	gfloat gain = 0.0;

	self.loop = g_main_loop_new (NULL, FALSE);

	self.am = audiomixer_new ();

	if (!self.am) {
		g_warning ("unable to open audiomixer");
		goto exit;
	}

	audiomixer_lock (self.am);
	ret = audiomixer_ensure_controls (self.am, 3);
	audiomixer_unlock (self.am);
	if (ret < 0) {
		g_warning ("ensure controls failed");
		goto exit;
	}

	audiomixer_events.controls_changed = mixer_controls_changed;
	audiomixer_add_event_listener (self.am, &audiomixer_events, (void *)&self);

	g_debug ("waiting for controls to be available");

	do {
		self.ctrls = audiomixer_get_active_controls (self.am, &self.nctrls);

		/*
		 * not a clean check but it appears like this is the best we can do at the
		 * moment.
		 */
		if (self.nctrls <= 4)
			/* end points are not registered, wait for them to show up */
			g_main_loop_run (self.loop);
		else
			break;

	} while (1);

	if (argc == 1) {
		print_ctrls (&self);
		show_help ();
		return 0;
	}

	audiomixer_events.value_changed = mixer_value_change_cb;
	audiomixer_add_event_listener (self.am, &audiomixer_events, (void *)&self);

	static const struct option long_options[] = {
		{ "help",						no_argument,				NULL, 'h' },
		{ "print-controls",	no_argument,				NULL, 'p' },
		{ "id",							required_argument,	NULL, 'i' },
		{ "set-volume",			required_argument,	NULL, 'v' },
		{ "set-mute",				no_argument,				NULL, 'm' },
		{ "set-gain",				required_argument,	NULL, 'g' },
		{ NULL,	0, NULL, 0}
	};

	while ((c = getopt_long (argc, argv, "hpi:v:mg:", long_options, NULL)) != -1) {
		switch(c) {
		case 'h':
			show_help ();
			break;

		case 'p':
			print_ctrls (&self);
			break;

		case 'i':
			id = atoi (optarg);
			if (!(id >= 0 && id < self.nctrls)) {
				ret = -1;
				g_warning ("id(%d) is invalid", id);
			}
			break;

		case 'v':
			vol = (double)atof (optarg);
			if (id == -1) {
				g_warning ("control id not given defaulting it to 0(Master Playback)");
				id = 0;
			}

			ret = set_volume (&self, id, vol);
			if (ret != TRUE)
				g_warning ("set-volume failed");
			else
				/* wait for volume to be acked */
				g_main_loop_run (self.loop);

			break;

		case 'm':
			if (id == -1) {
				g_warning ("control id not given defaulting it to 0(Master Playback)");
				id = 0;
			}

			ret = set_mute (&self, id);
			if (ret != TRUE)
				g_warning ("set-mute failed");
			else
				/* wait for mute to be acked */
				g_main_loop_run (self.loop);

			break;

		case 'g':
			gain = atof (optarg);
			if (id == -1) {
				g_warning ("control id not given defaulting it to 11(bass)");
				id = 11; /* bass ctrl */
			}

			ret = set_gain (&self, id, gain);
			if (ret != TRUE)
				g_warning ("set-gain failed");
			else
				/* wait for gain to be acked */
				g_main_loop_run (self.loop);

			break;

		default:
			show_help ();
			break;
		}
	}

exit:
	/* clean up at program exit */
	audio_mixer_clear (&self);
	return ret;
}