/*
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  Support for the verb/device/modifier core logic and API,
 *  command line tool and file parser was kindly sponsored by
 *  Texas Instruments Inc.
 *  Support for multiple active modifiers and devices,
 *  transition sequences, multiple client access and user defined use
 *  cases was kindly sponsored by Wolfson Microelectronics PLC.
 *
 *  Copyright (C) 2008-2010 SlimLogic Ltd
 *  Copyright (C) 2010 Wolfson Microelectronics PLC
 *  Copyright (C) 2010 Texas Instruments Inc.
 *  Copyright (C) 2010 Red Hat Inc.
 *  Authors: Liam Girdwood <lrg@slimlogic.co.uk>
 *	         Stefan Schmidt <stefan@slimlogic.co.uk>
 *	         Justin Xu <justinx@slimlogic.co.uk>
 *               Jaroslav Kysela <perex@perex.cz>
 */

#include "local.h"
#include <pthread.h>
#include <stdbool.h>
#include "use-case.h"

#define SYNTAX_VERSION_MAX	8

#define MAX_CARD_SHORT_NAME	32
#define MAX_CARD_LONG_NAME	80

#define SEQUENCE_ELEMENT_TYPE_CDEV		1
#define SEQUENCE_ELEMENT_TYPE_CSET		2
#define SEQUENCE_ELEMENT_TYPE_SLEEP		3
#define SEQUENCE_ELEMENT_TYPE_EXEC		4
#define SEQUENCE_ELEMENT_TYPE_SHELL		5
#define SEQUENCE_ELEMENT_TYPE_CSET_BIN_FILE	6
#define SEQUENCE_ELEMENT_TYPE_CSET_TLV		7
#define SEQUENCE_ELEMENT_TYPE_CSET_NEW		8
#define SEQUENCE_ELEMENT_TYPE_CTL_REMOVE	9
#define SEQUENCE_ELEMENT_TYPE_CMPT_SEQ		10
#define SEQUENCE_ELEMENT_TYPE_SYSSET		11
#define SEQUENCE_ELEMENT_TYPE_CFGSAVE		12
#define SEQUENCE_ELEMENT_TYPE_DEV_ENABLE_SEQ	13
#define SEQUENCE_ELEMENT_TYPE_DEV_DISABLE_SEQ	14
#define SEQUENCE_ELEMENT_TYPE_DEV_DISABLE_ALL	15

struct ucm_value {
	struct list_head list;
	char *name;
	char *data;
};

/* sequence of a component device */
struct component_sequence {
	struct use_case_device *device; /* component device */
	int enable; /* flag to choose enable or disable list of the device */
};

struct sequence_element {
	struct list_head list;
	unsigned int type;
	union {
		long sleep; /* Sleep time in microseconds if sleep element, else 0 */
		char *cdev;
		char *cset;
		char *exec;
		char *sysw;
		char *cfgsave;
		char *device;
		struct component_sequence cmpt_seq; /* component sequence */
	} data;
};

/*
 * Transition sequences. i.e. transition between one verb, device, mod to another
 */
struct transition_sequence {
	struct list_head list;
	char *name;
	struct list_head transition_list;
};

/*
 * Modifier Supported Devices.
 */
enum dev_list_type {
	DEVLIST_NONE,
	DEVLIST_SUPPORTED,
	DEVLIST_CONFLICTING
};

struct dev_list_node {
	struct list_head list;
	char *name;
};

struct dev_list {
	enum dev_list_type type;
	struct list_head list;
};

struct ctl_dev {
	struct list_head list;
	char *device;
};

struct ctl_list {
	struct list_head list;
	struct list_head dev_list;
	snd_ctl_t *ctl;
	snd_ctl_card_info_t *ctl_info;
	int slave;
	int ucm_group;
};

struct ucm_dev_name {
	struct list_head list;
	char *name1;
	char *name2;
};

/*
 * Describes a Use Case Modifier and it's enable and disable sequences.
 * A use case verb can have N modifiers.
 */
struct use_case_modifier {
	struct list_head list;
	struct list_head active_list;

	char *name;
	char *comment;

	/* modifier enable and disable sequences */
	struct list_head enable_list;
	struct list_head disable_list;

	/* modifier transition list */
	struct list_head transition_list;

	/* list of devices supported or conflicting */
	struct dev_list dev_list;

	/* values */
	struct list_head value_list;
};

/*
 * Describes a Use Case Device and it's enable and disable sequences.
 * A use case verb can have N devices.
 */
struct use_case_device {
	struct list_head list;
	struct list_head active_list;

	char *name;
	char *orig_name;
	char *comment;

	/* device enable and disable sequences */
	struct list_head enable_list;
	struct list_head disable_list;

	/* device transition list */
	struct list_head transition_list;

	/* list of devices supported or conflicting */
	struct dev_list dev_list;

	/* value list */
	struct list_head value_list;

	/* cached priority for sorting (LONG_MIN if not determined) */
	long sort_priority;

	/* list of variant devices */
	struct list_head variants;

	/* list link for variant devices */
	struct list_head variant_list;
};

/*
 * Describes a Use Case Verb and it's enable and disable sequences.
 * A use case verb can have N devices and N modifiers.
 */
struct use_case_verb {
	struct list_head list;

	unsigned int active: 1;

	char *name;
	char *comment;

	/* verb enable and disable sequences */
	struct list_head enable_list;
	struct list_head disable_list;

	/* verb transition list */
	struct list_head transition_list;

	struct list_head device_list;

	/* component device list */
	struct list_head cmpt_device_list;

	/* modifiers that can be used with this use case */
	struct list_head modifier_list;

	/* value list */
	struct list_head value_list;

	/* temporary modifications lists */
	struct list_head rename_list;
	struct list_head remove_list;
};

/*
 *  Manages a sound card and all its use cases.
 */
struct snd_use_case_mgr {
	char *card_name;
	char *conf_file_name;
	char *conf_dir_name;
	char *comment;
	int conf_format;
	unsigned int ucm_card_number;
	int suppress_nodev_errors;
	const char *parse_variant;
	int parse_master_section;
	int sequence_hops;
	bool in_boot;
	bool card_group;

	/* UCM cards list */
	struct list_head cards_list;

	/* use case verb, devices and modifier configs parsed from files */
	struct list_head verb_list;

	/* force boot settings - sequence */
	struct list_head fixedboot_list;

	/* boot settings - sequence */
	struct list_head boot_list;

	/* default settings - sequence */
	struct list_head default_list;
	int default_list_executed;

	/* default settings - value list */
	struct list_head value_list;

	/* global value list */
	struct list_head global_value_list;

	/* current status */
	struct use_case_verb *active_verb;
	struct list_head active_devices;
	struct list_head active_modifiers;

	/* locking */
	pthread_mutex_t mutex;

	/* UCM internal variables defined in configuration files */
	struct list_head variable_list;

	/* list of opened control devices */
	struct list_head ctl_list;

	/* tree with macros */
	snd_config_t *macros;
	int macro_hops;

	/* local library configuration */
	snd_config_t *local_config;

	/* Components don't define cdev, the card device. When executing
	 * a sequence of a component device, ucm manager enters component
	 * domain and needs to provide cdev to the component. This cdev
	 * should be defined by the machine, parent of the component.
	 */
	int in_component_domain;
	char *cdev;
};

void uc_mgr_error(const char *fmt, ...);
void uc_mgr_stdout(const char *fmt, ...);

const char *uc_mgr_sysfs_root(void);
const char *uc_mgr_config_dir(int format);
int uc_mgr_config_load_into(int format, const char *file, snd_config_t *cfg);
int uc_mgr_config_load(int format, const char *file, snd_config_t **cfg);
int uc_mgr_config_load_file(snd_use_case_mgr_t *uc_mgr,  const char *file, snd_config_t **cfg);
int uc_mgr_import_master_config(snd_use_case_mgr_t *uc_mgr);
int uc_mgr_scan_master_configs(const char **_list[]);

int uc_mgr_put_to_dev_list(struct dev_list *dev_list, const char *name);
int uc_mgr_remove_device(struct use_case_verb *verb, const char *name);
int uc_mgr_rename_device(struct use_case_verb *verb, const char *src,
			 const char *dst);

void uc_mgr_free_dev_name_list(struct list_head *base);
void uc_mgr_free_sequence_element(struct sequence_element *seq);
void uc_mgr_free_transition_element(struct transition_sequence *seq);
void uc_mgr_free_verb(snd_use_case_mgr_t *uc_mgr);
void uc_mgr_free(snd_use_case_mgr_t *uc_mgr);

static inline int uc_mgr_has_local_config(snd_use_case_mgr_t *uc_mgr)
{
	return uc_mgr && uc_mgr->local_config &&
			 snd_config_iterator_first(uc_mgr->local_config) !=
			 snd_config_iterator_end(uc_mgr->local_config);
}

static inline const char *uc_mgr_enable_str(bool enable)
{
	return enable ? "enable" : "disable";
}

int uc_mgr_card_open(snd_use_case_mgr_t *uc_mgr);
void uc_mgr_card_close(snd_use_case_mgr_t *uc_mgr);

int uc_mgr_open_ctl(snd_use_case_mgr_t *uc_mgr,
		    struct ctl_list **ctl_list,
		    const char *device,
		    int slave);

struct ctl_list *uc_mgr_get_master_ctl(snd_use_case_mgr_t *uc_mgr);
struct ctl_list *uc_mgr_get_ctl_by_card(snd_use_case_mgr_t *uc_mgr, int card);
struct ctl_list *uc_mgr_get_ctl_by_name(snd_use_case_mgr_t *uc_mgr,
					const char *name, int idx);
snd_ctl_t *uc_mgr_get_ctl(snd_use_case_mgr_t *uc_mgr);
void uc_mgr_free_ctl_list(snd_use_case_mgr_t *uc_mgr);
int uc_mgr_card_number(struct ctl_list *list);

void uc_mgr_free_value(struct list_head *base);

int uc_mgr_add_value(struct list_head *base, const char *key, char *val);

int uc_mgr_check_value(struct list_head *value_list, const char *identifier);

const char *uc_mgr_get_variable(snd_use_case_mgr_t *uc_mgr,
				const char *name);

int uc_mgr_set_variable(snd_use_case_mgr_t *uc_mgr,
			const char *name,
			const char *val);

int uc_mgr_delete_variable(snd_use_case_mgr_t *uc_mgr, const char *name);

int uc_mgr_duplicate_variables(struct list_head *dst, struct list_head *src);

int uc_mgr_get_substituted_value(snd_use_case_mgr_t *uc_mgr,
				 char **_rvalue,
				 const char *value);

int uc_mgr_substitute_tree(snd_use_case_mgr_t *uc_mgr,
			   snd_config_t *node);

int uc_mgr_config_tree_merge(snd_use_case_mgr_t *uc_mgr,
			     snd_config_t *parent, snd_config_t *new_ctx,
			     snd_config_t *before, snd_config_t *after);

int uc_mgr_evaluate_inplace(snd_use_case_mgr_t *uc_mgr,
			    snd_config_t *cfg);

int uc_mgr_evaluate_include(snd_use_case_mgr_t *uc_mgr,
			    snd_config_t *parent,
			    snd_config_t *inc);

int uc_mgr_evaluate_condition(snd_use_case_mgr_t *uc_mgr,
			      snd_config_t *parent,
			      snd_config_t *cond);

int uc_mgr_define_regex(snd_use_case_mgr_t *uc_mgr,
			const char *name,
			snd_config_t *eval);

int uc_mgr_exec(const char *prog);

/** The name of the environment variable containing the UCM directory */
#define ALSA_CONFIG_UCM_VAR "ALSA_CONFIG_UCM"

/** The name of the environment variable containing the UCM directory (new syntax) */
#define ALSA_CONFIG_UCM2_VAR "ALSA_CONFIG_UCM2"
