/* Copyright (c) 2012 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 <alsa/asoundlib.h>
#include <alsa/use-case.h>
#include <ctype.h>
#include <string.h>
#include <syslog.h>

#include "cras_alsa_ucm.h"
#include "utlist.h"

static const char jack_var[] = "JackName";
static const char jack_type_var[] = "JackType";
static const char jack_switch_var[] = "JackSwitch";
static const char edid_var[] = "EDIDFile";
static const char cap_var[] = "CaptureControl";
static const char mic_positions[] = "MicPositions";
static const char override_type_name_var[] = "OverrideNodeType";
static const char output_dsp_name_var[] = "OutputDspName";
static const char input_dsp_name_var[] = "InputDspName";
static const char mixer_var[] = "MixerName";
static const char swap_mode_suffix[] = "Swap Mode";
static const char min_buffer_level_var[] = "MinBufferLevel";
static const char dma_period_var[] = "DmaPeriodMicrosecs";
static const char disable_software_volume[] = "DisableSoftwareVolume";
static const char playback_device_name_var[] = "PlaybackPCM";
static const char playback_device_rate_var[] = "PlaybackRate";
static const char capture_device_name_var[] = "CapturePCM";
static const char capture_device_rate_var[] = "CaptureRate";
static const char capture_channel_map_var[] = "CaptureChannelMap";
static const char coupled_mixers[] = "CoupledMixers";
/* Set this value in a SectionDevice to specify the maximum software gain in dBm
 * and enable software gain on this node. */
static const char max_software_gain[] = "MaxSoftwareGain";
/* Set this value in a SectionDevice to specify the default node gain in dBm. */
static const char default_node_gain[] = "DefaultNodeGain";
static const char hotword_model_prefix[] = "Hotword Model";
static const char fully_specified_ucm_var[] = "FullySpecifiedUCM";
static const char main_volume_names[] = "MainVolumeNames";
static const char enable_htimestamp_var[] = "EnableHtimestamp";

/* Use case verbs corresponding to CRAS_STREAM_TYPE. */
static const char *use_case_verbs[] = {
	"HiFi",
	"Multimedia",
	"Voice Call",
	"Speech",
	"Pro Audio",
};

/* Represents a list of section names found in UCM. */
struct section_name {
	const char* name;
	struct section_name  *prev, *next;
};

struct cras_use_case_mgr {
	snd_use_case_mgr_t *mgr;
	const char *name;
	unsigned int avail_use_cases;
	enum CRAS_STREAM_TYPE use_case;
};

static inline const char *uc_verb(struct cras_use_case_mgr *mgr)
{
	return use_case_verbs[mgr->use_case];
}

static int device_enabled(struct cras_use_case_mgr *mgr, const char *dev)
{
	const char **list;
	unsigned int i;
	int num_devs;
	int enabled = 0;

	num_devs = snd_use_case_get_list(mgr->mgr, "_enadevs", &list);
	if (num_devs <= 0)
		return 0;

	for (i = 0; i < (unsigned int)num_devs; i++)
		if (!strcmp(dev, list[i])) {
			enabled = 1;
			break;
		}

	snd_use_case_free_list(list, num_devs);
	return enabled;
}

static int modifier_enabled(struct cras_use_case_mgr *mgr, const char *mod)
{
	const char **list;
	unsigned int mod_idx;
	int num_mods;

	num_mods = snd_use_case_get_list(mgr->mgr, "_enamods", &list);
	if (num_mods <= 0)
		return 0;

	for (mod_idx = 0; mod_idx < (unsigned int)num_mods; mod_idx++)
		if (!strcmp(mod, list[mod_idx]))
			break;

	snd_use_case_free_list(list, num_mods);
	return (mod_idx < (unsigned int)num_mods);
}

static int get_var(struct cras_use_case_mgr *mgr, const char *var,
		   const char *dev, const char *verb, const char **value)
{
	char *id;
	int rc;
	size_t len = strlen(var) + strlen(dev) + strlen(verb) + 4;

	id = (char *)malloc(len);
	if (!id)
		return -ENOMEM;
	snprintf(id, len, "=%s/%s/%s", var, dev, verb);
	rc = snd_use_case_get(mgr->mgr, id, value);

	free((void *)id);
	return rc;
}

static int get_int(struct cras_use_case_mgr *mgr, const char *var,
		   const char *dev, const char *verb, int *value)
{
	const char *str_value;
	int rc;

	if (!value)
		return -EINVAL;
	rc = get_var(mgr, var, dev, verb, &str_value);
	if (rc != 0)
		return rc;
	*value = atoi(str_value);
	free((void *)str_value);
	return 0;
}

static int ucm_set_modifier_enabled(struct cras_use_case_mgr *mgr,
				    const char *mod, int enable)
{
	return snd_use_case_set(mgr->mgr, enable ? "_enamod" : "_dismod", mod);
}

static int ucm_str_ends_with_suffix(const char *str, const char *suffix)
{
	if (!str || !suffix)
		return 0;
	size_t len_str = strlen(str);
	size_t len_suffix = strlen(suffix);
	if (len_suffix > len_str)
		return 0;
	return strncmp(str + len_str - len_suffix, suffix, len_suffix) == 0;
}

static int ucm_section_exists_with_name(struct cras_use_case_mgr *mgr,
		const char *name, const char *identifier)
{
	const char **list;
	unsigned int i;
	int num_entries;
	int exist = 0;

	num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list);
	if (num_entries <= 0)
		return 0;

	for (i = 0; i < (unsigned int)num_entries; i+=2) {

		if (!list[i])
			continue;

		if (strcmp(list[i], name) == 0) {
			exist = 1;
			break;
		}
	}
	snd_use_case_free_list(list, num_entries);
	return exist;
}

static int ucm_section_exists_with_suffix(struct cras_use_case_mgr *mgr,
		const char *suffix, const char *identifier)
{
	const char **list;
	unsigned int i;
	int num_entries;
	int exist = 0;

	num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list);
	if (num_entries <= 0)
		return 0;

	for (i = 0; i < (unsigned int)num_entries; i+=2) {

		if (!list[i])
			continue;

		if (ucm_str_ends_with_suffix(list[i], suffix)) {
			exist = 1;
			break;
		}
	}
	snd_use_case_free_list(list, num_entries);
	return exist;
}

static int ucm_mod_exists_with_suffix(struct cras_use_case_mgr *mgr,
				      const char *suffix)
{
	char *identifier;
	int rc;

	identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr));
	rc = ucm_section_exists_with_suffix(mgr, suffix, identifier);
	free(identifier);
	return rc;
}

static int ucm_mod_exists_with_name(struct cras_use_case_mgr *mgr,
				    const char *name)
{
	char *identifier;
	int rc;

	identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr));
	rc = ucm_section_exists_with_name(mgr, name, identifier);
	free(identifier);
	return rc;
}

/* Get a list of section names whose variable is the matched value. */
static struct section_name * ucm_get_sections_for_var(
		struct cras_use_case_mgr *mgr,
		const char *var, const char *value,
		const char *identifier,
		enum CRAS_STREAM_DIRECTION direction)
{
	const char **list;
	struct section_name *section_names = NULL, *s_name;
	unsigned int i;
	int num_entries;
	int rc;

	num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list);
	if (num_entries <= 0)
		return NULL;

	/* snd_use_case_get_list fills list with pairs of device name and
	 * comment, so device names are in even-indexed elements. */
	for (i = 0; i < (unsigned int)num_entries; i+=2) {
		const char *this_value;

		if (!list[i])
			continue;

		rc = get_var(mgr, var, list[i], uc_verb(mgr), &this_value);
		if (rc)
			continue;

		if (!strcmp(value, this_value)) {
			s_name = (struct section_name *)malloc(
					sizeof(struct section_name));

			if (!s_name) {
				syslog(LOG_ERR, "Failed to allocate memory");
				free((void *)this_value);
				break;
			}

			s_name->name = strdup(list[i]);
			DL_APPEND(section_names, s_name);
		}
		free((void *)this_value);
	}

	snd_use_case_free_list(list, num_entries);
	return section_names;
}

static struct section_name *ucm_get_devices_for_var(
		struct cras_use_case_mgr *mgr,
		const char *var, const char *value,
		enum CRAS_STREAM_DIRECTION dir)
{
	char *identifier;
	struct section_name *section_names;

	identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr));
	section_names = ucm_get_sections_for_var(mgr, var, value, identifier,
						 dir);
	free(identifier);
	return section_names;
}

static const char *ucm_get_playback_device_name_for_dev(
		struct cras_use_case_mgr *mgr, const char *dev)
{
	const char *name = NULL;
	int rc;

	rc = get_var(mgr, playback_device_name_var, dev, uc_verb(mgr), &name);
	if (rc)
		return NULL;

	return name;
}

static const char *ucm_get_capture_device_name_for_dev(
		struct cras_use_case_mgr *mgr, const char *dev)
{
	const char *name = NULL;
	int rc;

	rc = get_var(mgr, capture_device_name_var, dev, uc_verb(mgr), &name);
	if (rc)
		return NULL;

	return name;
}

/* Get a list of mixer names specified in a UCM variable separated by ",".
 * E.g. "Left Playback,Right Playback".
 */
static struct mixer_name *ucm_get_mixer_names(struct cras_use_case_mgr *mgr,
				const char *dev, const char* var,
				enum CRAS_STREAM_DIRECTION dir,
				mixer_name_type type)
{
	const char *names_in_string = NULL;
	int rc;
	char *tokens, *name, *laststr;
	struct mixer_name *names = NULL;

	rc = get_var(mgr, var, dev, uc_verb(mgr), &names_in_string);
	if (rc)
		return NULL;

	tokens = strdup(names_in_string);
	name = strtok_r(tokens, ",", &laststr);
	while (name != NULL) {
		names = mixer_name_add(names, name, dir, type);
		name = strtok_r(NULL, ",", &laststr);
	}
	free((void*)names_in_string);
	free(tokens);
	return names;
}

/* Exported Interface */

struct cras_use_case_mgr *ucm_create(const char *name)
{
	struct cras_use_case_mgr *mgr;
	int rc;
	const char **list;
	int num_verbs, i, j;

	if (!name)
		return NULL;

	mgr = (struct cras_use_case_mgr *)malloc(sizeof(*mgr));
	if (!mgr)
		return NULL;

	rc = snd_use_case_mgr_open(&mgr->mgr, name);
	if (rc) {
		syslog(LOG_WARNING, "Can not open ucm for card %s, rc = %d",
		       name, rc);
		goto cleanup;
	}

	mgr->name = name;
	mgr->avail_use_cases = 0;
	num_verbs = snd_use_case_get_list(mgr->mgr, "_verbs", &list);
	for (i = 0; i < num_verbs; i += 2) {
		for (j = 0; j < CRAS_STREAM_NUM_TYPES; ++j) {
			if (strcmp(list[i], use_case_verbs[j]) == 0)
				break;
		}
		if (j < CRAS_STREAM_NUM_TYPES)
			mgr->avail_use_cases |= (1 << j);
	}
	if (num_verbs > 0)
		snd_use_case_free_list(list, num_verbs);

	rc = ucm_set_use_case(mgr, CRAS_STREAM_TYPE_DEFAULT);
	if (rc)
		goto cleanup_mgr;

	return mgr;

cleanup_mgr:
	snd_use_case_mgr_close(mgr->mgr);
cleanup:
	free(mgr);
	return NULL;
}

void ucm_destroy(struct cras_use_case_mgr *mgr)
{
	snd_use_case_mgr_close(mgr->mgr);
	free(mgr);
}

int ucm_set_use_case(struct cras_use_case_mgr *mgr,
		     enum CRAS_STREAM_TYPE use_case)
{
	int rc;

	if (mgr->avail_use_cases & (1 << use_case)) {
		mgr->use_case = use_case;
	} else {
		syslog(LOG_ERR, "Unavailable use case %d for card %s",
		       use_case, mgr->name);
		return -1;
	}

	rc = snd_use_case_set(mgr->mgr, "_verb", uc_verb(mgr));
	if (rc) {
		syslog(LOG_ERR, "Can not set verb %s for card %s, rc = %d",
		       uc_verb(mgr), mgr->name, rc);
		return rc;
	}

	return 0;
}

int ucm_swap_mode_exists(struct cras_use_case_mgr *mgr)
{
	return ucm_mod_exists_with_suffix(mgr, swap_mode_suffix);
}

int ucm_enable_swap_mode(struct cras_use_case_mgr *mgr, const char *node_name,
			 int enable)
{
	char *swap_mod = NULL;
	int rc;
	size_t len = strlen(node_name) + 1 + strlen(swap_mode_suffix) + 1;
	swap_mod = (char *)malloc(len);
	if (!swap_mod)
		return -ENOMEM;
	snprintf(swap_mod, len, "%s %s", node_name, swap_mode_suffix);
	if (!ucm_mod_exists_with_name(mgr, swap_mod)) {
		syslog(LOG_ERR, "Can not find swap mode modifier %s.", swap_mod);
		free((void *)swap_mod);
		return -EPERM;
	}
	if (modifier_enabled(mgr, swap_mod) == !!enable) {
		free((void *)swap_mod);
		return 0;
	}
	rc = ucm_set_modifier_enabled(mgr, swap_mod, enable);
	free((void *)swap_mod);
	return rc;
}

int ucm_set_enabled(struct cras_use_case_mgr *mgr, const char *dev, int enable)
{
	if (device_enabled(mgr, dev) == !!enable)
		return 0;
	syslog(LOG_DEBUG, "UCM %s %s", enable ? "enable" : "disable", dev);
	return snd_use_case_set(mgr->mgr, enable ? "_enadev" : "_disdev", dev);
}

char *ucm_get_flag(struct cras_use_case_mgr *mgr, const char *flag_name)
{
	char *flag_value = NULL;
	const char *value;
	int rc;

	/* Set device to empty string since flag is specified in verb section */
	rc = get_var(mgr, flag_name, "", uc_verb(mgr), &value);
	if (!rc) {
		flag_value = strdup(value);
		free((void *)value);
	}

	return flag_value;
}

char *ucm_get_cap_control(struct cras_use_case_mgr *mgr, const char *ucm_dev)
{
	char *control_name = NULL;
	const char *value;
	int rc;

	rc = get_var(mgr, cap_var, ucm_dev, uc_verb(mgr), &value);
	if (!rc) {
		control_name = strdup(value);
		free((void *)value);
	}

	return control_name;
}

char *ucm_get_mic_positions(struct cras_use_case_mgr *mgr)
{
	char *control_name = NULL;
	const char *value;
	int rc;

	rc = get_var(mgr, mic_positions, "", uc_verb(mgr), &value);
	if (!rc) {
		control_name = strdup(value);
		free((void *)value);
	}

	return control_name;
}

const char *ucm_get_override_type_name(struct cras_use_case_mgr *mgr,
				       const char *dev)
{
	const char *override_type_name;
	int rc;

	rc = get_var(mgr, override_type_name_var, dev, uc_verb(mgr),
		     &override_type_name);
	if (rc)
		return NULL;

	return override_type_name;
}

char *ucm_get_dev_for_jack(struct cras_use_case_mgr *mgr, const char *jack,
			   enum CRAS_STREAM_DIRECTION direction)
{
	struct section_name *section_names, *c;
	char *ret = NULL;

	section_names = ucm_get_devices_for_var(mgr, jack_var, jack, direction);

	DL_FOREACH(section_names, c) {
		if (!strcmp(c->name, "Mic")) {
			/* Skip mic section for output */
			if (direction == CRAS_STREAM_OUTPUT)
				continue;
		} else {
			/* Only check mic for input. */
			if (direction == CRAS_STREAM_INPUT)
				continue;
		}
		ret = strdup(c->name);
		break;
	}

	DL_FOREACH(section_names, c) {
		DL_DELETE(section_names, c);
		free((void*)c->name);
		free(c);
	}

	return ret;
}

char *ucm_get_dev_for_mixer(struct cras_use_case_mgr *mgr, const char *mixer,
			    enum CRAS_STREAM_DIRECTION dir)
{
	struct section_name *section_names, *c;
	char *ret = NULL;

	section_names = ucm_get_devices_for_var(mgr, mixer_var, mixer, dir);

	if (section_names)
		ret = strdup(section_names->name);

	DL_FOREACH(section_names, c) {
		DL_DELETE(section_names, c);
		free((void*)c->name);
		free(c);
	}

	return ret;
}

const char *ucm_get_edid_file_for_dev(struct cras_use_case_mgr *mgr,
				      const char *dev)
{
	const char *file_name;
	int rc;

	rc = get_var(mgr, edid_var, dev, uc_verb(mgr), &file_name);
	if (rc)
		return NULL;

	return file_name;
}

const char *ucm_get_dsp_name(struct cras_use_case_mgr *mgr, const char *ucm_dev,
			     int direction)
{
	const char *var = (direction == CRAS_STREAM_OUTPUT)
		? output_dsp_name_var
		: input_dsp_name_var;
	const char *dsp_name = NULL;
	int rc;

	rc = get_var(mgr, var, ucm_dev, uc_verb(mgr), &dsp_name);
	if (rc)
		return NULL;

	return dsp_name;
}

const char *ucm_get_dsp_name_default(struct cras_use_case_mgr *mgr,
				     int direction)
{
	return ucm_get_dsp_name(mgr, "", direction);
}

unsigned int ucm_get_min_buffer_level(struct cras_use_case_mgr *mgr)
{
	int value;
	int rc;

	rc = get_int(mgr, min_buffer_level_var, "", uc_verb(mgr), &value);
	if (rc)
		return 0;

	return value;
}

unsigned int ucm_get_disable_software_volume(struct cras_use_case_mgr *mgr)
{
	int value;
	int rc;

	rc = get_int(mgr, disable_software_volume, "", uc_verb(mgr), &value);
	if (rc)
		return 0;

	return value;
}

int ucm_get_max_software_gain(struct cras_use_case_mgr *mgr, const char *dev,
			      long *gain)
{
	int value;
	int rc;

	rc = get_int(mgr, max_software_gain, dev, uc_verb(mgr), &value);
	if (rc)
		return rc;
	*gain = value;
	return 0;
}

int ucm_get_default_node_gain(struct cras_use_case_mgr *mgr, const char *dev,
			      long *gain)
{
	int value;
	int rc;

	rc = get_int(mgr, default_node_gain, dev, uc_verb(mgr), &value);
	if (rc)
		return rc;
	*gain = value;
	return 0;
}

const char *ucm_get_device_name_for_dev(
	struct cras_use_case_mgr *mgr, const char *dev,
	enum CRAS_STREAM_DIRECTION direction)
{
	if (direction == CRAS_STREAM_OUTPUT)
		return ucm_get_playback_device_name_for_dev(mgr, dev);
	else if (direction == CRAS_STREAM_INPUT)
		return ucm_get_capture_device_name_for_dev(mgr, dev);
	return NULL;
}

int ucm_get_sample_rate_for_dev(struct cras_use_case_mgr *mgr, const char *dev,
				enum CRAS_STREAM_DIRECTION direction)
{
	int value;
	int rc;
	const char *var_name;

	if (direction == CRAS_STREAM_OUTPUT)
		var_name = playback_device_rate_var;
	else if (direction == CRAS_STREAM_INPUT)
		var_name = capture_device_rate_var;
	else
		return -EINVAL;

	rc = get_int(mgr, var_name, dev, uc_verb(mgr), &value);
	if (rc)
		return rc;

	return value;
}

int ucm_get_capture_chmap_for_dev(struct cras_use_case_mgr *mgr,
				  const char *dev,
				  int8_t *channel_layout)
{
	const char *var_str;
	char *tokens, *token;
	int i, rc;

	rc = get_var(mgr, capture_channel_map_var, dev, uc_verb(mgr), &var_str);
	if (rc)
		return rc;

	tokens = strdup(var_str);
	token = strtok(tokens, " ");
	for (i = 0; token && (i < CRAS_CH_MAX); i++) {
		channel_layout[i] = atoi(token);
		token = strtok(NULL, " ");
	}

	free((void *)tokens);
	free((void *)var_str);
	return (i == CRAS_CH_MAX) ? 0 : -EINVAL;
}

struct mixer_name *ucm_get_coupled_mixer_names(
		struct cras_use_case_mgr *mgr, const char *dev)
{
	return ucm_get_mixer_names(mgr, dev, coupled_mixers,
				   CRAS_STREAM_OUTPUT,
				   MIXER_NAME_VOLUME);
}

static int get_device_index_from_target(const char *target_device_name)
{
	/* Expects a string in the form: hw:card-name,<num> */
	const char *pos = target_device_name;
	if (!pos)
		return -1;
	while (*pos && *pos != ',')
		++pos;
	if (*pos == ',') {
		++pos;
		return atoi(pos);
	}
	return -1;
}

struct ucm_section *ucm_get_sections(struct cras_use_case_mgr *mgr)
{
	struct ucm_section *sections = NULL;
	struct ucm_section *dev_sec;
	const char **list;
	int num_devs;
	int i;
	char *identifier;

	/* Find the list of all mixers using the control names defined in
	 * the header definintion for this function.  */
	identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr));
	num_devs = snd_use_case_get_list(mgr->mgr, identifier, &list);
	free(identifier);

	/* snd_use_case_get_list fills list with pairs of device name and
	 * comment, so device names are in even-indexed elements. */
	for (i = 0; i < num_devs; i += 2) {
		enum CRAS_STREAM_DIRECTION dir = CRAS_STREAM_UNDEFINED;
		int dev_idx = -1;
		const char *dev_name = strdup(list[i]);
		const char *jack_name;
		const char *jack_type;
		const char *mixer_name;
		struct mixer_name *m_name;
		int rc;
		const char *target_device_name;

		if (!dev_name)
			continue;

		target_device_name =
			ucm_get_playback_device_name_for_dev(mgr, dev_name);
		if (target_device_name)
			dir = CRAS_STREAM_OUTPUT;
		else {
			target_device_name =
				ucm_get_capture_device_name_for_dev(
					mgr, dev_name);
			if (target_device_name)
				dir = CRAS_STREAM_INPUT;
		}
		if (target_device_name) {
			dev_idx = get_device_index_from_target(
					target_device_name);
			free((void *)target_device_name);
		}

		if (dir == CRAS_STREAM_UNDEFINED) {
			syslog(LOG_ERR,
			       "UCM configuration for device '%s' missing"
			       " PlaybackPCM or CapturePCM definition.",
			       dev_name);
			goto error_cleanup;
		}

		if (dev_idx == -1) {
			syslog(LOG_ERR,
			       "PlaybackPCM or CapturePCM for '%s' must be in"
			       " the form 'hw:<card>,<number>'", dev_name);
			goto error_cleanup;
		}

		jack_name = ucm_get_jack_name_for_dev(mgr, dev_name);
		jack_type = ucm_get_jack_type_for_dev(mgr, dev_name);
		mixer_name = ucm_get_mixer_name_for_dev(mgr, dev_name);

		dev_sec = ucm_section_create(dev_name, dev_idx, dir,
					     jack_name, jack_type);
		if (jack_name)
			free((void *)jack_name);
		if (jack_type)
			free((void *)jack_type);

		if (!dev_sec) {
			syslog(LOG_ERR, "Failed to allocate memory.");
			if (mixer_name)
				free((void *)mixer_name);
			goto error_cleanup;
		}

		dev_sec->jack_switch =
			ucm_get_jack_switch_for_dev(mgr, dev_name);

		if (mixer_name) {
			rc = ucm_section_set_mixer_name(dev_sec, mixer_name);
			free((void *)mixer_name);
			if (rc)
				goto error_cleanup;
		}

		m_name = ucm_get_mixer_names(mgr, dev_name, coupled_mixers,
					     dir, MIXER_NAME_VOLUME);
		ucm_section_concat_coupled(dev_sec, m_name);

		DL_APPEND(sections, dev_sec);
		ucm_section_dump(dev_sec);
	}

	if (num_devs > 0)
		snd_use_case_free_list(list, num_devs);
	return sections;

error_cleanup:
	if (num_devs > 0)
		snd_use_case_free_list(list, num_devs);
	ucm_section_free_list(sections);
	return NULL;
}

char *ucm_get_hotword_models(struct cras_use_case_mgr *mgr)
{
	const char **list;
	int i, num_entries;
	int models_len = 0;
	char *models = NULL;
	const char *tmp;
	char *identifier;

	identifier = snd_use_case_identifier("_modifiers/%s", uc_verb(mgr));
	num_entries = snd_use_case_get_list(mgr->mgr, identifier, &list);
	free(identifier);
	if (num_entries <= 0)
		return 0;
	models = (char *)malloc(num_entries * 8);
	for (i = 0; i < num_entries; i+=2) {
		if (!list[i])
			continue;
		if (0 == strncmp(list[i], hotword_model_prefix,
				 strlen(hotword_model_prefix))) {
			tmp = list[i] + strlen(hotword_model_prefix);
			while (isspace(*tmp))
				tmp++;
			strcpy(models + models_len, tmp);
			models_len += strlen(tmp);
			if (i + 2 >= num_entries)
				models[models_len] = '\0';
			else
				models[models_len++] = ',';
		}
	}
	snd_use_case_free_list(list, num_entries);

	return models;
}

int ucm_set_hotword_model(struct cras_use_case_mgr *mgr, const char *model)
{
	const char **list;
	int num_enmods, mod_idx;
	char *model_mod = NULL;
	size_t model_mod_size = strlen(model) + 1 +
				strlen(hotword_model_prefix) + 1;
	model_mod = (char *)malloc(model_mod_size);
	if (!model_mod)
		return -ENOMEM;
	snprintf(model_mod, model_mod_size,
		 "%s %s", hotword_model_prefix, model);
	if (!ucm_mod_exists_with_name(mgr, model_mod)) {
		free((void *)model_mod);
		return -EINVAL;
	}

	/* Disable all currently enabled horword model modifiers. */
	num_enmods = snd_use_case_get_list(mgr->mgr, "_enamods", &list);
	if (num_enmods <= 0)
		goto enable_mod;

	for (mod_idx = 0; mod_idx < num_enmods; mod_idx++) {
		if (!strncmp(list[mod_idx], hotword_model_prefix,
			     strlen(hotword_model_prefix)))
			ucm_set_modifier_enabled(mgr, list[mod_idx], 0);
	}
	snd_use_case_free_list(list, num_enmods);

enable_mod:
	ucm_set_modifier_enabled(mgr, model_mod, 1);

	return 0;
}

int ucm_has_fully_specified_ucm_flag(struct cras_use_case_mgr *mgr)
{
	char *flag;
	int ret = 0;
	flag = ucm_get_flag(mgr, fully_specified_ucm_var);
	if (!flag)
		return 0;
	ret = !strcmp(flag, "1");
	free(flag);
	return ret;
}

const char *ucm_get_mixer_name_for_dev(struct cras_use_case_mgr *mgr, const char *dev)
{
	const char *name = NULL;
	int rc;

	rc = get_var(mgr, mixer_var, dev, uc_verb(mgr), &name);
	if (rc)
		return NULL;

	return name;
}

struct mixer_name *ucm_get_main_volume_names(struct cras_use_case_mgr *mgr)
{
	return ucm_get_mixer_names(mgr, "", main_volume_names,
				   CRAS_STREAM_OUTPUT, MIXER_NAME_MAIN_VOLUME);
}

int ucm_list_section_devices_by_device_name(
		struct cras_use_case_mgr *mgr,
		enum CRAS_STREAM_DIRECTION direction,
		const char *device_name,
		ucm_list_section_devices_callback cb,
		void *cb_arg)
{
	int listed= 0;
	struct section_name *section_names, *c;
	const char* var;
	char *identifier;

	if (direction == CRAS_STREAM_OUTPUT)
		var = playback_device_name_var;
	else if (direction == CRAS_STREAM_INPUT)
		var = capture_device_name_var;
	else
		return 0;

	identifier = snd_use_case_identifier("_devices/%s", uc_verb(mgr));
	section_names = ucm_get_sections_for_var(
		mgr, var, device_name, identifier, direction);
	free(identifier);
	if (!section_names)
		return 0;

	DL_FOREACH(section_names, c) {
		cb(c->name, cb_arg);
		listed++;
	}

	DL_FOREACH(section_names, c) {
		DL_DELETE(section_names, c);
		free((void*)c->name);
		free(c);
	}
	return listed;
}

const char *ucm_get_jack_name_for_dev(struct cras_use_case_mgr *mgr,
				      const char *dev)
{
	const char *name = NULL;
	int rc;

	rc = get_var(mgr, jack_var, dev, uc_verb(mgr), &name);
	if (rc)
		return NULL;

	return name;
}

const char *ucm_get_jack_type_for_dev(struct cras_use_case_mgr *mgr,
				      const char *dev)
{
	const char *name = NULL;
	int rc;

	rc = get_var(mgr, jack_type_var, dev, uc_verb(mgr), &name);
	if (rc)
		return NULL;

	if (strcmp(name, "hctl") && strcmp(name, "gpio")) {
		syslog(LOG_ERR, "Unknown jack type: %s", name);
		return NULL;
	}
	return name;
}

int ucm_get_jack_switch_for_dev(struct cras_use_case_mgr *mgr, const char *dev)
{
	int value;

	int rc = get_int(mgr, jack_switch_var, dev, uc_verb(mgr), &value);
	if (rc || value < 0)
		return -1;
	return value;
}

unsigned int ucm_get_dma_period_for_dev(struct cras_use_case_mgr *mgr,
					const char *dev)
{
	int value;

	int rc = get_int(mgr, dma_period_var, dev, uc_verb(mgr), &value);
	if (rc || value < 0)
		return 0;
	return value;
}

unsigned int ucm_get_enable_htimestamp_flag(struct cras_use_case_mgr *mgr)
{
	char *flag;
	int ret = 0;
	flag = ucm_get_flag(mgr, enable_htimestamp_var);
	if (!flag)
		return 0;
	ret = !strcmp(flag, "1");
	free(flag);
	return ret;
}