/*
  Copyright(c) 2014-2015 Intel Corporation
  All rights reserved.

  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.1 of
  the License, or (at your option) any later version.

  This program 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.

  Authors: Mengdong Lin <mengdong.lin@intel.com>
           Yao Jin <yao.jin@intel.com>
           Liam Girdwood <liam.r.girdwood@linux.intel.com>
*/

#include "tplg_local.h"

struct tplg_table tplg_table[] = {
	{
		.name  = "manifest",
		.id    = "SectionManifest",
		.loff  = offsetof(snd_tplg_t, manifest_list),
		.type  = SND_TPLG_TYPE_MANIFEST,
		.tsoc  = SND_SOC_TPLG_TYPE_MANIFEST,
		.size  = sizeof(struct snd_soc_tplg_manifest),
		.enew  = 1,
		.parse = tplg_parse_manifest_data,
		.save  = tplg_save_manifest_data,
		.decod = tplg_decode_manifest_data,
	},
	{
		.name  = "control mixer",
		.id    = "SectionControlMixer",
		.loff  = offsetof(snd_tplg_t, mixer_list),
		.type  = SND_TPLG_TYPE_MIXER,
		.tsoc  = SND_SOC_TPLG_TYPE_MIXER,
		.size  = sizeof(struct snd_soc_tplg_mixer_control),
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_control_mixer,
		.save  = tplg_save_control_mixer,
		.decod = tplg_decode_control_mixer,
	},
	{
		.name  = "control enum",
		.id    = "SectionControlEnum",
		.loff  = offsetof(snd_tplg_t, enum_list),
		.type  = SND_TPLG_TYPE_ENUM,
		.tsoc  = SND_SOC_TPLG_TYPE_ENUM,
		.size  = sizeof(struct snd_soc_tplg_enum_control),
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_control_enum,
		.save  = tplg_save_control_enum,
		.decod = tplg_decode_control_enum,
	},
	{
		.name  = "control extended (bytes)",
		.id    = "SectionControlBytes",
		.loff  = offsetof(snd_tplg_t, bytes_ext_list),
		.type  = SND_TPLG_TYPE_BYTES,
		.tsoc  = SND_SOC_TPLG_TYPE_BYTES,
		.size  = sizeof(struct snd_soc_tplg_bytes_control),
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_control_bytes,
		.save  = tplg_save_control_bytes,
		.decod = tplg_decode_control_bytes,
	},
	{
		.name  = "dapm widget",
		.id    = "SectionWidget",
		.loff  = offsetof(snd_tplg_t, widget_list),
		.type  = SND_TPLG_TYPE_DAPM_WIDGET,
		.tsoc  = SND_SOC_TPLG_TYPE_DAPM_WIDGET,
		.size  = sizeof(struct snd_soc_tplg_dapm_widget),
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_dapm_widget,
		.save  = tplg_save_dapm_widget,
		.decod = tplg_decode_dapm_widget,
	},
	{
		.name  = "pcm",
		.id    = "SectionPCM",
		.loff  = offsetof(snd_tplg_t, pcm_list),
		.type  = SND_TPLG_TYPE_PCM,
		.tsoc  = SND_SOC_TPLG_TYPE_PCM,
		.size  = sizeof(struct snd_soc_tplg_pcm),
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_pcm,
		.save  = tplg_save_pcm,
		.decod = tplg_decode_pcm,
	},
	{
		.name  = "physical dai",
		.id    = "SectionDAI",
		.loff  = offsetof(snd_tplg_t, dai_list),
		.type  = SND_TPLG_TYPE_DAI,
		.tsoc  = SND_SOC_TPLG_TYPE_DAI,
		.size  = sizeof(struct snd_soc_tplg_dai),
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_dai,
		.save  = tplg_save_dai,
		.decod = tplg_decode_dai,
	},
	{
		.name  = "be",
		.id    = "SectionBE",
		.id2   = "SectionLink",
		.loff  = offsetof(snd_tplg_t, be_list),
		.type  = SND_TPLG_TYPE_BE,
		.tsoc  = SND_SOC_TPLG_TYPE_BACKEND_LINK,
		.size  = sizeof(struct snd_soc_tplg_link_config),
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_link,
		.save  = tplg_save_link,
		.decod = tplg_decode_link,
	},
	{
		.name  = "cc",
		.id    = "SectionCC",
		.loff  = offsetof(snd_tplg_t, cc_list),
		.type  = SND_TPLG_TYPE_CC,
		.tsoc  = SND_SOC_TPLG_TYPE_CODEC_LINK,
		.size  = sizeof(struct snd_soc_tplg_link_config),
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_cc,
		.save  = tplg_save_cc,
		.decod = tplg_decode_cc,
	},
	{
		.name  = "route (dapm graph)",
		.id = "SectionGraph",
		.loff  = offsetof(snd_tplg_t, route_list),
		.type  = SND_TPLG_TYPE_DAPM_GRAPH,
		.tsoc  = SND_SOC_TPLG_TYPE_DAPM_GRAPH,
		.build = 1,
		.parse = tplg_parse_dapm_graph,
		.gsave = tplg_save_dapm_graph,
		.decod = tplg_decode_dapm_graph,
	},
	{
		.name  = "private data",
		.id    = "SectionData",
		.loff  = offsetof(snd_tplg_t, pdata_list),
		.type  = SND_TPLG_TYPE_DATA,
		.tsoc  = SND_SOC_TPLG_TYPE_PDATA,
		.build = 1,
		.enew  = 1,
		.parse = tplg_parse_data,
		.save  = tplg_save_data,
		.decod = tplg_decode_data,
	},
	{
		.name  = "text",
		.id    = "SectionText",
		.loff  = offsetof(snd_tplg_t, text_list),
		.type  = SND_TPLG_TYPE_TEXT,
		.size  = sizeof(struct tplg_texts),
		.enew  = 1,
		.parse = tplg_parse_text,
		.save  = tplg_save_text,
	},
	{
		.name  = "tlv",
		.id    = "SectionTLV",
		.loff  = offsetof(snd_tplg_t, tlv_list),
		.type  = SND_TPLG_TYPE_TLV,
		.size  = sizeof(struct snd_soc_tplg_ctl_tlv),
		.enew  = 1,
		.parse = tplg_parse_tlv,
		.save  = tplg_save_tlv,
	},
	{
		.name  = "stream config",
		.loff  = offsetof(snd_tplg_t, pcm_config_list),
		.type  = SND_TPLG_TYPE_STREAM_CONFIG,
		.size  = sizeof(struct snd_soc_tplg_stream),
		.enew  = 1,
	},
	{
		.name  = "stream capabilities",
		.id    = "SectionPCMCapabilities",
		.loff  = offsetof(snd_tplg_t, pcm_caps_list),
		.type  = SND_TPLG_TYPE_STREAM_CAPS,
		.size  = sizeof(struct snd_soc_tplg_stream_caps),
		.enew  = 1,
		.parse = tplg_parse_stream_caps,
		.save  = tplg_save_stream_caps,
	},
	{
		.name  = "token",
		.id    = "SectionVendorTokens",
		.loff  = offsetof(snd_tplg_t, token_list),
		.type  = SND_TPLG_TYPE_TOKEN,
		.enew  = 1,
		.parse = tplg_parse_tokens,
		.save  = tplg_save_tokens,
	},
	{
		.name  = "tuple",
		.id    = "SectionVendorTuples",
		.loff  = offsetof(snd_tplg_t, tuple_list),
		.type  = SND_TPLG_TYPE_TUPLE,
		.free  = tplg_free_tuples,
		.enew  = 1,
		.parse = tplg_parse_tuples,
		.save  = tplg_save_tuples,
	},
	{
		.name  = "hw config",
		.id    = "SectionHWConfig",
		.loff  = offsetof(snd_tplg_t, hw_cfg_list),
		.type  = SND_TPLG_TYPE_HW_CONFIG,
		.size  = sizeof(struct snd_soc_tplg_hw_config),
		.enew  = 1,
		.parse = tplg_parse_hw_config,
		.save  = tplg_save_hw_config,
	}
};

unsigned int tplg_table_items = ARRAY_SIZE(tplg_table);

int tplg_get_type(int asoc_type)
{
	unsigned int index;

	for (index = 0; index < tplg_table_items; index++)
		if (tplg_table[index].tsoc == asoc_type)
			return tplg_table[index].type;
	SNDERR("uknown asoc type %d", asoc_type);
	return -EINVAL;
}

int tplg_ref_add(struct tplg_elem *elem, int type, const char* id)
{
	struct tplg_ref *ref;

	ref = calloc(1, sizeof(*ref));
	if (!ref)
		return -ENOMEM;

	strncpy(ref->id, id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
	ref->id[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0;
	ref->type = type;

	list_add_tail(&ref->list, &elem->ref_list);
	return 0;
}

/* directly add a reference elem */
int tplg_ref_add_elem(struct tplg_elem *elem, struct tplg_elem *elem_ref)
{
	struct tplg_ref *ref;

	ref = calloc(1, sizeof(*ref));
	if (!ref)
		return -ENOMEM;

	ref->type = elem_ref->type;
	ref->elem = elem_ref;
	snd_strlcpy(ref->id, elem_ref->id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);

	list_add_tail(&ref->list, &elem->ref_list);
	return 0;
}

void tplg_ref_free_list(struct list_head *base)
{
	struct list_head *pos, *npos;
	struct tplg_ref *ref;

	list_for_each_safe(pos, npos, base) {
		ref = list_entry(pos, struct tplg_ref, list);
		list_del(&ref->list);
		free(ref);
	}
}

struct tplg_elem *tplg_elem_new(void)
{
	struct tplg_elem *elem;

	elem = calloc(1, sizeof(*elem));
	if (!elem)
		return NULL;

	INIT_LIST_HEAD(&elem->ref_list);
	return elem;
}

void tplg_elem_free(struct tplg_elem *elem)
{
	list_del(&elem->list);

	tplg_ref_free_list(&elem->ref_list);

	/* free struct snd_tplg_ object,
	 * the union pointers share the same address
	 */
	if (elem->obj) {
		if (elem->free)
			elem->free(elem->obj);

		free(elem->obj);
	}

	free(elem);
}

void tplg_elem_free_list(struct list_head *base)
{
	struct list_head *pos, *npos;
	struct tplg_elem *elem;

	list_for_each_safe(pos, npos, base) {
		elem = list_entry(pos, struct tplg_elem, list);
		tplg_elem_free(elem);
	}
}

struct tplg_elem *tplg_elem_lookup(struct list_head *base, const char* id,
				   unsigned int type, int index)
{
	struct list_head *pos;
	struct tplg_elem *elem;

	if (!base || !id)
		return NULL;

	list_for_each(pos, base) {

		elem = list_entry(pos, struct tplg_elem, list);

		if (!strcmp(elem->id, id) && elem->type == type)
			return elem;
		/* SND_TPLG_INDEX_ALL is the default value "0" and applicable
		   for all use cases */
		if ((index != SND_TPLG_INDEX_ALL)
			&& (elem->index > index))
			break;
	}

	return NULL;
}

/* find an element by type */
struct tplg_elem *tplg_elem_type_lookup(snd_tplg_t *tplg,
					enum snd_tplg_type type)
{
	struct tplg_table *tptr;
	struct list_head *pos, *list;
	struct tplg_elem *elem;
	unsigned int index;

	for (index = 0; index < tplg_table_items; index++) {
		tptr = &tplg_table[index];
		if (!tptr->enew)
			continue;
		if ((int)type != tptr->type)
			continue;
		break;
	}
	if (index >= tplg_table_items)
		return NULL;

	list = (struct list_head *)((void *)tplg + tptr->loff);

	/* return only first element */
	list_for_each(pos, list) {
		elem = list_entry(pos, struct tplg_elem, list);
		return elem;
	}
	return NULL;
}

/* insert a new element into list in the ascending order of index value */
void tplg_elem_insert(struct tplg_elem *elem_p, struct list_head *list)
{
	struct list_head *pos, *p = &(elem_p->list);
	struct tplg_elem *elem;

	list_for_each(pos, list) {
		elem = list_entry(pos, struct tplg_elem, list);
		if (elem_p->index < elem->index)
			break;
	}
	/* insert item before pos */
	list_insert(p, pos->prev, pos);
}

/* create a new common element and object */
struct tplg_elem* tplg_elem_new_common(snd_tplg_t *tplg,
				       snd_config_t *cfg,
				       const char *name,
				       enum snd_tplg_type type)
{
	struct tplg_table *tptr;
	struct tplg_elem *elem;
	struct list_head *list;
	const char *id;
	int obj_size = 0;
	unsigned index;
	void *obj;
	snd_config_iterator_t i, next;
	snd_config_t *n;

	if (!cfg && !name)
		return NULL;

	elem = tplg_elem_new();
	if (!elem)
		return NULL;

	/* do we get name from cfg */
	if (cfg) {
		snd_config_get_id(cfg, &id);
		snd_strlcpy(elem->id, id, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);
		elem->id[SNDRV_CTL_ELEM_ID_NAME_MAXLEN - 1] = 0;
		/* as we insert new elem based on the index value, move index
		   parsing here */
		snd_config_for_each(i, next, cfg) {
			n = snd_config_iterator_entry(i);
			if (snd_config_get_id(n, &id))
				continue;
			if (strcmp(id, "index") == 0) {
				if (tplg_get_integer(n, &elem->index, 0)) {
					free(elem);
					return NULL;
				}
				if (elem->index < 0) {
					free(elem);
					return NULL;
				}
			}
		}
	} else if (name != NULL)
		snd_strlcpy(elem->id, name, SNDRV_CTL_ELEM_ID_NAME_MAXLEN);

	for (index = 0; index < tplg_table_items; index++) {
		tptr = &tplg_table[index];
		if (!tptr->enew)
			continue;
		if ((int)type != tptr->type)
			continue;
		break;
	}
	if (index >= tplg_table_items) {
		free(elem);
		return NULL;
	}

	list = (struct list_head *)((void *)tplg + tptr->loff);
	tplg_elem_insert(elem, list);
	obj_size = tptr->size;
	elem->free = tptr->free;
	elem->table = tptr;

	/* create new object too if required */
	if (obj_size > 0) {
		obj = calloc(1, obj_size);
		if (obj == NULL) {
			free(elem);
			return NULL;
		}

		elem->obj = obj;
		elem->size = obj_size;
	}

	elem->type = type;
	return elem;
}

#ifndef DOC_HIDDEN
struct tplg_alloc {
	struct list_head list;
	void *data[0];
};
#endif /* DOC_HIDDEN */

void *tplg_calloc(struct list_head *heap, size_t size)
{
	struct tplg_alloc *a;

	a = calloc(1, sizeof(*a) + size);
	if (a == NULL)
		return NULL;
	list_add_tail(&a->list, heap);
	return a->data;
}

void tplg_free(struct list_head *heap)
{
	struct list_head *pos, *npos;
	struct tplg_alloc *a;

	list_for_each_safe(pos, npos, heap) {
		a = list_entry(pos, struct tplg_alloc, list);
		list_del(&a->list);
		free(a);
	}
}
