/* ALSA path select test tool */
/* SPDX-FileCopyrightText: Copyright © 2025 Daniel Nouri */
/* SPDX-License-Identifier: MIT */

/*
 * Tests pa_alsa_path_select() to verify mute switches are correctly set
 * based on device mute status, regardless of mute_during_activation setting.
 *
 * Regression test for HDMI/DisplayPort audio bug where IEC958 switches
 * remain in kernel default state (OFF) because pa_alsa_path_select() only
 * set switches when mute_during_activation=true, but no paths enable this.
 *
 * Test verification:
 *   Before fix: IEC958 switch stays OFF after path select → test FAILS
 *   After fix:  IEC958 switch set correctly based on device state → test PASSES
 *
 * Usage:
 *   test-alsa-path-select -c <card> [-p <path>] [-m] [-v]
 *
 * Examples:
 *   test-alsa-path-select -c 1                  # IEC958 unmuted (expect ON)
 *   test-alsa-path-select -c 1 -m               # IEC958 muted (expect OFF)
 *   test-alsa-path-select -c 0 -p analog-output # Regression test
 *
 * Exit codes:
 *   0 = Test passed
 *   1 = Test failed
 *   2 = Setup error
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <getopt.h>
#include <alsa/asoundlib.h>

#include "alsa-mixer.h"
#include "alsa-util.h"

#define DEFAULT_PATHS_DIR "/usr/share/alsa-card-profile/mixer/paths"

struct test_config {
	int card;
	const char *path_name;
	const char *paths_dir;
	bool device_muted;
	bool verbose;
};

static void show_help(const char *name)
{
	printf("Usage: %s [options]\n", name);
	printf("\n");
	printf("Test tool for pa_alsa_path_select() - verifies IEC958 unmute fix\n");
	printf("\n");
	printf("Options:\n");
	printf("  -c, --card <N>         ALSA card number (required)\n");
	printf("  -p, --path <name>      Mixer path name (default: iec958-stereo-output)\n");
	printf("  -d, --paths-dir <dir>  Path to mixer path configs (default: %s)\n", DEFAULT_PATHS_DIR);
	printf("  -m, --muted            Test with device muted (default: unmuted)\n");
	printf("  -v, --verbose          Verbose output\n");
	printf("  -h, --help             Show this help\n");
	printf("\n");
	printf("Examples:\n");
	printf("  # Test IEC958 unmute on card 1\n");
	printf("  %s -c 1\n", name);
	printf("\n");
	printf("  # Test analog output\n");
	printf("  %s -c 0 -p analog-output\n", name);
	printf("\n");
	printf("  # Test with device muted\n");
	printf("  %s -c 1 -m\n", name);
	printf("\n");
	printf("Exit codes:\n");
	printf("  0 - Test passed (switches set correctly)\n");
	printf("  1 - Test failed (switches not set as expected)\n");
	printf("  2 - Setup error (path not found, hardware error, etc.)\n");
}

static int parse_args(int argc, char *argv[], struct test_config *config)
{
	static const struct option long_options[] = {
		{ "card",      required_argument, NULL, 'c' },
		{ "path",      required_argument, NULL, 'p' },
		{ "paths-dir", required_argument, NULL, 'd' },
		{ "muted",     no_argument,       NULL, 'm' },
		{ "verbose",   no_argument,       NULL, 'v' },
		{ "help",      no_argument,       NULL, 'h' },
		{ NULL, 0, NULL, 0 }
	};

	int c;
	config->card = -1;
	config->path_name = "iec958-stereo-output.conf";
	config->paths_dir = DEFAULT_PATHS_DIR;
	config->device_muted = false;
	config->verbose = false;

	while ((c = getopt_long(argc, argv, "c:p:d:mvh", long_options, NULL)) != -1) {
		switch (c) {
		case 'c':
			config->card = atoi(optarg);
			break;
		case 'p':
			config->path_name = optarg;
			break;
		case 'd':
			config->paths_dir = optarg;
			break;
		case 'm':
			config->device_muted = true;
			break;
		case 'v':
			config->verbose = true;
			break;
		case 'h':
			show_help(argv[0]);
			return -1;
		default:
			show_help(argv[0]);
			return -2;
		}
	}

	if (config->card < 0) {
		fprintf(stderr, "Error: Card number is required (-c <N>)\n\n");
		show_help(argv[0]);
		return -2;
	}

	return 0;
}

static int get_switch_state(snd_mixer_t *mixer, const char *name, int index, bool *state)
{
	snd_mixer_elem_t *elem;
	snd_mixer_selem_id_t *sid;
	int val;

	snd_mixer_selem_id_alloca(&sid);
	snd_mixer_selem_id_set_name(sid, name);
	snd_mixer_selem_id_set_index(sid, index);

	elem = snd_mixer_find_selem(mixer, sid);
	if (!elem) {
		fprintf(stderr, "Warning: Element '%s',%d not found\n", name, index);
		return -ENOENT;
	}

	if (!snd_mixer_selem_has_playback_switch(elem)) {
		fprintf(stderr, "Warning: Element '%s',%d has no playback switch\n", name, index);
		return -EINVAL;
	}

	if (snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO, &val) < 0) {
		if (snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_FRONT_LEFT, &val) < 0) {
			fprintf(stderr, "Error: Cannot read switch state for '%s',%d\n", name, index);
			return -EIO;
		}
	}

	*state = val ? true : false;
	return 0;
}

int main(int argc, char *argv[])
{
	struct test_config config;
	pa_alsa_path *path = NULL;
	snd_mixer_t *mixer = NULL;
	char card_name[64];
	int ret = 2;  // Setup error by default
	int err;

	/* Parse arguments */
	err = parse_args(argc, argv, &config);
	if (err < 0) {
		return err == -1 ? 0 : 2;  // -1 is help, -2 is error
	}

	printf("Testing pa_alsa_path_select() with:\n");
	printf("  Card: %d\n", config.card);
	printf("  Path: %s\n", config.path_name);
	printf("  Paths dir: %s\n", config.paths_dir);
	printf("  Device muted: %s\n", config.device_muted ? "yes" : "no");
	printf("\n");

	/* Load mixer path */
	path = pa_alsa_path_new(config.paths_dir, config.path_name, PA_ALSA_DIRECTION_OUTPUT);
	if (!path) {
		fprintf(stderr, "Error: Failed to load path '%s' from %s\n",
			config.path_name, config.paths_dir);
		fprintf(stderr, "Make sure the path file exists: %s/%s.conf\n",
			config.paths_dir, config.path_name);
		goto cleanup;
	}

	/* Count elements BEFORE probing */
	int element_count_before = 0;
	pa_alsa_element *e_temp;
	PA_LLIST_FOREACH(e_temp, path->elements) {
		element_count_before++;
	}
	printf("Elements in path BEFORE probing: %d\n", element_count_before);

	if (config.verbose && element_count_before > 0) {
		printf("Loaded path: %s\n", path->name ? path->name : config.path_name);
		printf("  mute_during_activation: %s\n", path->mute_during_activation ? "yes" : "no");
		printf("  Elements:\n");

		PA_LLIST_FOREACH(e_temp, path->elements) {
			const char *switch_use;
			switch (e_temp->switch_use) {
			case PA_ALSA_SWITCH_MUTE: switch_use = "mute"; break;
			case PA_ALSA_SWITCH_ON: switch_use = "on"; break;
			case PA_ALSA_SWITCH_OFF: switch_use = "off"; break;
			default: switch_use = "ignore"; break;
			}
			printf("    - %s (index %d): switch=%s\n",
				e_temp->alsa_id.name, e_temp->alsa_id.index, switch_use);
		}
		printf("\n");
	}

	/* Open ALSA mixer */
	snprintf(card_name, sizeof(card_name), "hw:%d", config.card);

	if (snd_mixer_open(&mixer, 0) < 0) {
		fprintf(stderr, "Error: Failed to open mixer\n");
		goto cleanup;
	}

	if (snd_mixer_attach(mixer, card_name) < 0) {
		fprintf(stderr, "Error: Failed to attach mixer to %s\n", card_name);
		fprintf(stderr, "Make sure card %d exists (check with: aplay -l)\n", config.card);
		goto cleanup;
	}

	if (snd_mixer_selem_register(mixer, NULL, NULL) < 0) {
		fprintf(stderr, "Error: Failed to register mixer\n");
		goto cleanup;
	}

	if (snd_mixer_load(mixer) < 0) {
		fprintf(stderr, "Error: Failed to load mixer elements\n");
		goto cleanup;
	}

	/* Probe the path to ensure elements are available */
	printf("Probing path...\n");
	if (pa_alsa_path_probe(path, NULL, mixer, false) < 0) {
		fprintf(stderr, "Error: Path probe failed - required elements not found on card\n");
		goto cleanup;
	}
	printf("Path probe: OK\n\n");

	/* Record switch states before activation */
	printf("Switch states BEFORE pa_alsa_path_select():\n");
	pa_alsa_element *e;
	int mute_switch_count = 0;
	PA_LLIST_FOREACH(e, path->elements) {
		if (e->switch_use == PA_ALSA_SWITCH_MUTE) {
			bool state;
			mute_switch_count++;
			if (get_switch_state(mixer, e->alsa_id.name, e->alsa_id.index, &state) == 0) {
				printf("  %s,%d = %s\n", e->alsa_id.name, e->alsa_id.index,
					state ? "ON" : "OFF");
			}
		}
	}

	if (mute_switch_count == 0) {
		fprintf(stderr, "Warning: No mute switches found in path\n");
		printf("This test requires a path with switch=mute elements\n");
		goto cleanup;
	}

	/* Call pa_alsa_path_select() - THE FUNCTION UNDER TEST */
	printf("\nCalling pa_alsa_path_select(path, NULL, mixer, device_is_muted=%s)...\n",
		config.device_muted ? "true" : "false");

	err = pa_alsa_path_select(path, NULL, mixer, config.device_muted);
	if (err < 0) {
		fprintf(stderr, "Error: pa_alsa_path_select() failed with error %d\n", err);
		goto cleanup;
	}
	printf("pa_alsa_path_select(): OK\n\n");

	/* Verify switch states after activation */
	printf("Switch states AFTER pa_alsa_path_select():\n");
	bool all_correct = true;
	bool expected_state = !config.device_muted;  // unmuted device = switches ON

	PA_LLIST_FOREACH(e, path->elements) {
		if (e->switch_use == PA_ALSA_SWITCH_MUTE) {
			bool actual_state;
			if (get_switch_state(mixer, e->alsa_id.name, e->alsa_id.index, &actual_state) == 0) {
				printf("  %s,%d = %s", e->alsa_id.name, e->alsa_id.index,
					actual_state ? "ON" : "OFF");

				if (actual_state == expected_state) {
					printf(" ✓\n");
				} else {
					printf(" ✗ (expected %s)\n", expected_state ? "ON" : "OFF");
					all_correct = false;
				}
			}
		}
	}

	/* Determine test result */
	printf("\n");
	printf("========================================\n");
	if (all_correct) {
		printf("TEST PASSED\n");
		printf("All mute switches correctly set to %s\n", expected_state ? "ON" : "OFF");
		if (!path->mute_during_activation) {
			printf("\nThis proves the fix works:\n");
			printf("- Path has mute_during_activation=false (default)\n");
			printf("- Switches were still set correctly\n");
			printf("- Bug would have left them in kernel default state\n");
		}
		ret = 0;
	} else {
		printf("TEST FAILED\n");
		printf("One or more switches NOT set correctly\n");
		printf("Expected: %s, but some switches were %s\n",
			expected_state ? "ON (unmuted)" : "OFF (muted)",
			expected_state ? "OFF" : "ON");
		printf("\nThis indicates the bug is still present:\n");
		printf("- Switches not set when mute_during_activation=false\n");
		printf("- IEC958 fix may not be applied\n");
		ret = 1;
	}
	printf("========================================\n");

cleanup:
	if (mixer)
		snd_mixer_close(mixer);
	if (path)
		pa_alsa_path_free(path);

	return ret;
}
