/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 2010, Digium, Inc.
 *
 * David Vossel <dvossel@digium.com>
 *
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */

/*!
 * \file
 * \brief Format Capability API
 *
 * \author David Vossel <dvossel@digium.com>
 */

#include "asterisk.h"

ASTERISK_FILE_VERSION(__FILE__, "$Revision$");

#include "asterisk/_private.h"
#include "asterisk/version.h"
#include "asterisk/format.h"
#include "asterisk/format_cap.h"
#include "asterisk/frame.h"
#include "asterisk/astobj2.h"
#include "asterisk/utils.h"


struct ast_format_cap {
	/* The capabilities structure is just an ao2 container of ast_formats */
	struct ao2_container *formats;
	struct ao2_iterator it;
	int nolock;
};

/*! format exists within capabilities structure if it is identical to
 * another format, or if the format is a proper subset of another format. */
static int cmp_cb(void *obj, void *arg, int flags)
{
	struct ast_format *format1 = arg;
	struct ast_format *format2 = obj;
	enum ast_format_cmp_res res = ast_format_cmp(format1, format2);

	return ((res == AST_FORMAT_CMP_EQUAL) ||
			(res == AST_FORMAT_CMP_SUBSET)) ?
				CMP_MATCH | CMP_STOP :
				0;
}

static int hash_cb(const void *obj, const int flags)
{
	const struct ast_format *format = obj;
	return format->id;
}

static struct ast_format_cap *cap_alloc_helper(int nolock)
{
	struct ast_format_cap *cap = ast_calloc(1, sizeof(*cap));

	if (!cap) {
		return NULL;
	}
	cap->nolock = nolock ? OBJ_NOLOCK : 0;
	if (!(cap->formats = ao2_container_alloc(283, hash_cb, cmp_cb))) {
		ast_free(cap);
		return NULL;
	}

	return cap;
}

struct ast_format_cap *ast_format_cap_alloc_nolock(void)
{
	return cap_alloc_helper(1);
}

struct ast_format_cap *ast_format_cap_alloc(void)
{
	return cap_alloc_helper(0);
}

void *ast_format_cap_destroy(struct ast_format_cap *cap)
{
	if (!cap) {
		return NULL;
	}
	ao2_ref(cap->formats, -1);
	ast_free(cap);
	return NULL;
}

void ast_format_cap_add(struct ast_format_cap *cap, const struct ast_format *format)
{
	struct ast_format *fnew;

	if (!format || !format->id) {
		return;
	}
	if (!(fnew = ao2_alloc(sizeof(struct ast_format), NULL))) {
		return;
	}
	ast_format_copy(fnew, format);
	if (cap->nolock) {
		ao2_link_nolock(cap->formats, fnew);
	} else {
		ao2_link(cap->formats, fnew);
	}
	ao2_ref(fnew, -1);
}

void ast_format_cap_add_all_by_type(struct ast_format_cap *cap, enum ast_format_type type)
{
	int x;
	size_t f_len = 0;
	const struct ast_format_list *f_list = ast_format_list_get(&f_len);

	for (x = 0; x < f_len; x++) {
		if (AST_FORMAT_GET_TYPE(f_list[x].format.id) == type) {
			ast_format_cap_add(cap, &f_list[x].format);
		}
	}
	ast_format_list_destroy(f_list);
}

void ast_format_cap_add_all(struct ast_format_cap *cap)
{
	int x;
	size_t f_len = 0;
	const struct ast_format_list *f_list = ast_format_list_get(&f_len);

	for (x = 0; x < f_len; x++) {
		ast_format_cap_add(cap, &f_list[x].format);
	}
	ast_format_list_destroy(f_list);
}

static int append_cb(void *obj, void *arg, int flag)
{
	struct ast_format_cap *result = (struct ast_format_cap *) arg;
	struct ast_format *format = (struct ast_format *) obj;

	if (!ast_format_cap_iscompatible(result, format)) {
		ast_format_cap_add(result, format);
	}

	return 0;
}

void ast_format_cap_append(struct ast_format_cap *dst, const struct ast_format_cap *src)
{
	ao2_callback(src->formats, OBJ_NODATA | src->nolock, append_cb, dst);
}

static int copy_cb(void *obj, void *arg, int flag)
{
	struct ast_format_cap *result = (struct ast_format_cap *) arg;
	struct ast_format *format = (struct ast_format *) obj;

	ast_format_cap_add(result, format);
	return 0;
}

void ast_format_cap_copy(struct ast_format_cap *dst, const struct ast_format_cap *src)
{
	ast_format_cap_remove_all(dst);
	ao2_callback(src->formats, OBJ_NODATA | src->nolock, copy_cb, dst);
}

struct ast_format_cap *ast_format_cap_dup(const struct ast_format_cap *cap)
{
	struct ast_format_cap *dst;
	if (cap->nolock) {
		dst = ast_format_cap_alloc_nolock();
	} else {
		dst = ast_format_cap_alloc();
	}
	if (!dst) {
		return NULL;
	}
	ao2_callback(cap->formats, OBJ_NODATA | cap->nolock, copy_cb, dst);
	return dst;
}

int ast_format_cap_is_empty(const struct ast_format_cap *cap)
{
	if (!cap) {
		return 1;
	}
	return ao2_container_count(cap->formats) == 0 ? 1 : 0;
}

static int find_exact_cb(void *obj, void *arg, int flag)
{
	struct ast_format *format1 = (struct ast_format *) arg;
	struct ast_format *format2 = (struct ast_format *) obj;

	return (ast_format_cmp(format1, format2) == AST_FORMAT_CMP_EQUAL) ? CMP_MATCH : 0;
}

int ast_format_cap_remove(struct ast_format_cap *cap, struct ast_format *format)
{
	struct ast_format *fremove;
	fremove = ao2_callback(cap->formats, OBJ_POINTER | OBJ_UNLINK | cap->nolock, find_exact_cb, format);

	if (fremove) {
		ao2_ref(fremove, -1);
		return 0;
	}

	return -1;
}

struct multiple_by_id_data {
	struct ast_format *format;
	int match_found;
};

static int multiple_by_id_cb(void *obj, void *arg, int flag)
{
	struct multiple_by_id_data *data = arg;
	struct ast_format *format = obj;
	int res;

	res = (format->id == data->format->id) ? CMP_MATCH : 0;
	if (res) {
		data->match_found = 1;
	}

	return res;
}

int ast_format_cap_remove_byid(struct ast_format_cap *cap, enum ast_format_id id)
{
	struct ast_format format = {
		.id = id,
	};
	struct multiple_by_id_data data = {
		.format = &format,
		.match_found = 0,
	};

	ao2_callback(cap->formats,
		OBJ_NODATA | cap->nolock | OBJ_MULTIPLE | OBJ_UNLINK,
		multiple_by_id_cb,
		&data);

	/* match_found will be set if at least one item was removed */
	if (data.match_found) {
		return 0;
	}

	return -1;
}

static int multiple_by_type_cb(void *obj, void *arg, int flag)
{
	int *type = arg;
	struct ast_format *format = obj;
	return ((AST_FORMAT_GET_TYPE(format->id)) == *type) ? CMP_MATCH : 0;
}

void ast_format_cap_remove_bytype(struct ast_format_cap *cap, enum ast_format_type type)
{
	ao2_callback(cap->formats,
		OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE | cap->nolock,
		multiple_by_type_cb,
		&type);
}

void ast_format_cap_remove_all(struct ast_format_cap *cap)
{
	ao2_callback(cap->formats, OBJ_NODATA | cap->nolock | OBJ_MULTIPLE | OBJ_UNLINK, NULL, NULL);
}

void ast_format_cap_set(struct ast_format_cap *cap, struct ast_format *format)
{
	ast_format_cap_remove_all(cap);
	ast_format_cap_add(cap, format);
}

int ast_format_cap_get_compatible_format(const struct ast_format_cap *cap, const struct ast_format *format, struct ast_format *result)
{
	struct ast_format *f;
	struct ast_format_cap *tmp_cap = (struct ast_format_cap *) cap;
	f = ao2_find(tmp_cap->formats, (struct ast_format *) format, OBJ_POINTER | tmp_cap->nolock);

	if (f) {
		ast_format_copy(result, f);
		ao2_ref(f, -1);
		return 1;
	}
	ast_format_clear(result);
	return 0;
}

int ast_format_cap_iscompatible(const struct ast_format_cap *cap, const struct ast_format *format)
{
	struct ast_format *f;
	struct ast_format_cap *tmp_cap = (struct ast_format_cap *) cap;
	f = ao2_find(tmp_cap->formats, (struct ast_format *) format, OBJ_POINTER | tmp_cap->nolock);

	if (f) {
		ao2_ref(f, -1);
		return 1;
	}

	return 0;
}

struct byid_data {
	struct ast_format *result;
	enum ast_format_id id;
};
static int find_best_byid_cb(void *obj, void *arg, int flag)
{
	struct ast_format *format = obj;
	struct byid_data *data = arg;

	if (data->id != format->id) {
		return 0;
	}
	if (!data->result->id || (ast_format_rate(data->result) < ast_format_rate(format))) {
		ast_format_copy(data->result, format);
	}
	return 0;
}

int ast_format_cap_best_byid(const struct ast_format_cap *cap, enum ast_format_id id, struct ast_format *result)
{
	struct byid_data data;
	data.result = result;
	data.id = id;

	ast_format_clear(result);
	ao2_callback(cap->formats,
		OBJ_MULTIPLE | OBJ_NODATA | cap->nolock,
		find_best_byid_cb,
		&data);
	return result->id ? 1 : 0;
}

/*! \internal
 * \brief this struct is just used for the ast_format_cap_joint function so we can provide
 * both a format and a result ast_format_cap structure as arguments to the find_joint_cb
 * ao2 callback function.
 */
struct find_joint_data {
	/*! format to compare to for joint capabilities */
	struct ast_format *format;
	/*! if joint formmat exists with above format, add it to the result container */
	struct ast_format_cap *joint_cap;
	int joint_found;
};

static int find_joint_cb(void *obj, void *arg, int flag)
{
	struct ast_format *format = obj;
	struct find_joint_data *data = arg;

	struct ast_format tmp = { 0, };
	if (!ast_format_joint(format, data->format, &tmp)) {
		if (data->joint_cap) {
			ast_format_cap_add(data->joint_cap, &tmp);
		}
		data->joint_found++;
	}

	return 0;
}

int ast_format_cap_has_joint(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
{
	struct ao2_iterator it;
	struct ast_format *tmp;
	struct find_joint_data data = {
		.joint_found = 0,
		.joint_cap = NULL,
	};

	it = ao2_iterator_init(cap1->formats, cap1->nolock ? AO2_ITERATOR_DONTLOCK : 0);
	while ((tmp = ao2_iterator_next(&it))) {
		data.format = tmp;
		ao2_callback(cap2->formats,
			OBJ_MULTIPLE | OBJ_NODATA | cap2->nolock,
			find_joint_cb,
			&data);
		ao2_ref(tmp, -1);
	}
	ao2_iterator_destroy(&it);

	return data.joint_found ? 1 : 0;
}

int ast_format_cap_identical(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
{
	struct ao2_iterator it;
	struct ast_format *tmp;

	if (ao2_container_count(cap1->formats) != ao2_container_count(cap2->formats)) {
		return 0; /* if they are not the same size, they are not identical */
	}

	it = ao2_iterator_init(cap1->formats, cap1->nolock ? AO2_ITERATOR_DONTLOCK : 0);
	while ((tmp = ao2_iterator_next(&it))) {
		if (!ast_format_cap_iscompatible(cap2, tmp)) {
			ao2_ref(tmp, -1);
			ao2_iterator_destroy(&it);
			return 0;
		}
		ao2_ref(tmp, -1);
	}
	ao2_iterator_destroy(&it);

	return 1;
}

struct ast_format_cap *ast_format_cap_joint(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2)
{
	struct ao2_iterator it;
	struct ast_format_cap *result = ast_format_cap_alloc_nolock();
	struct ast_format *tmp;
	struct find_joint_data data = {
		.joint_found = 0,
		.joint_cap = result,
	};
	if (!result) {
		return NULL;
	}

	it = ao2_iterator_init(cap1->formats, cap1->nolock ? AO2_ITERATOR_DONTLOCK : 0);
	while ((tmp = ao2_iterator_next(&it))) {
		data.format = tmp;
		ao2_callback(cap2->formats,
			OBJ_MULTIPLE | OBJ_NODATA | cap2->nolock,
			find_joint_cb,
			&data);
		ao2_ref(tmp, -1);
	}
	ao2_iterator_destroy(&it);

	if (ao2_container_count(result->formats)) {
		return result;
	}

	result = ast_format_cap_destroy(result);
	return NULL;
}

static int joint_copy_helper(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2, struct ast_format_cap *result, int append)
{
	struct ao2_iterator it;
	struct ast_format *tmp;
	struct find_joint_data data = {
		.joint_cap = result,
		.joint_found = 0,
	};
	if (!append) {
		ast_format_cap_remove_all(result);
	}
	it = ao2_iterator_init(cap1->formats, cap2->nolock ? AO2_ITERATOR_DONTLOCK : 0);
	while ((tmp = ao2_iterator_next(&it))) {
		data.format = tmp;
		ao2_callback(cap2->formats,
			OBJ_MULTIPLE | OBJ_NODATA | cap2->nolock,
			find_joint_cb,
			&data);
		ao2_ref(tmp, -1);
	}
	ao2_iterator_destroy(&it);

	return ao2_container_count(result->formats) ? 1 : 0;
}

int ast_format_cap_joint_append(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2, struct ast_format_cap *result)
{
	return joint_copy_helper(cap1, cap2, result, 1);
}

int ast_format_cap_joint_copy(const struct ast_format_cap *cap1, const struct ast_format_cap *cap2, struct ast_format_cap *result)
{
	return joint_copy_helper(cap1, cap2, result, 0);
}

struct ast_format_cap *ast_format_cap_get_type(const struct ast_format_cap *cap, enum ast_format_type ftype)
{
	struct ao2_iterator it;
	struct ast_format_cap *result = ast_format_cap_alloc_nolock();
	struct ast_format *tmp;

	if (!result) {
		return NULL;
	}

	/* for each format in cap1, see if that format is
	 * compatible with cap2. If so copy it to the result */
	it = ao2_iterator_init(cap->formats, cap->nolock ? AO2_ITERATOR_DONTLOCK : 0);
	while ((tmp = ao2_iterator_next(&it))) {
		if (AST_FORMAT_GET_TYPE(tmp->id) == ftype) {
			/* copy format */
			ast_format_cap_add(result, tmp);
		}
		ao2_ref(tmp, -1);
	}
	ao2_iterator_destroy(&it);

	if (ao2_container_count(result->formats)) {
		return result;
	}
	result = ast_format_cap_destroy(result);

	/* Remember to always free the NULL before returning it. */
	ast_free(NULL);
	return NULL;
}


int ast_format_cap_has_type(const struct ast_format_cap *cap, enum ast_format_type type)
{
	struct ao2_iterator it;
	struct ast_format *tmp;

	it = ao2_iterator_init(cap->formats, cap->nolock ? AO2_ITERATOR_DONTLOCK : 0);
	while ((tmp = ao2_iterator_next(&it))) {
		if (AST_FORMAT_GET_TYPE(tmp->id) == type) {
			ao2_ref(tmp, -1);
			ao2_iterator_destroy(&it);
			return 1;
		}
		ao2_ref(tmp, -1);
	}
	ao2_iterator_destroy(&it);

	return 0;
}

void ast_format_cap_iter_start(struct ast_format_cap *cap)
{
	if (!cap->nolock) {
		ao2_lock(cap->formats);
	}
	cap->it = ao2_iterator_init(cap->formats, cap->nolock ? AO2_ITERATOR_DONTLOCK : 0);
}

void ast_format_cap_iter_end(struct ast_format_cap *cap)
{
	ao2_iterator_destroy(&cap->it);
	if (!cap->nolock) {
		ao2_unlock(cap->formats);
	}
}

int ast_format_cap_iter_next(struct ast_format_cap *cap, struct ast_format *format)
{
	struct ast_format *tmp = ao2_iterator_next(&cap->it);

	if (!tmp) {
		return -1;
	}
	ast_format_copy(format, tmp);
	ao2_ref(tmp, -1);

	return 0;
}

char *ast_getformatname_multiple(char *buf, size_t size, struct ast_format_cap *cap)
{
	int x;
	unsigned len;
	char *start, *end = buf;
	struct ast_format tmp_fmt;
	size_t f_len;
	const struct ast_format_list *f_list = ast_format_list_get(&f_len);

	if (!size) {
		f_list = ast_format_list_destroy(f_list);
		return buf;
	}
	snprintf(end, size, "(");
	len = strlen(end);
	end += len;
	size -= len;
	start = end;
	for (x = 0; x < f_len; x++) {
		ast_format_copy(&tmp_fmt, &f_list[x].format);
		if (ast_format_cap_iscompatible(cap, &tmp_fmt)) {
			snprintf(end, size, "%s|", f_list[x].name);
			len = strlen(end);
			end += len;
			size -= len;
		}
	}
	if (start == end) {
		ast_copy_string(start, "nothing)", size);
	} else if (size > 1) {
		*(end - 1) = ')';
	}
	f_list = ast_format_list_destroy(f_list);
	return buf;
}

uint64_t ast_format_cap_to_old_bitfield(const struct ast_format_cap *cap)
{
	uint64_t res = 0;
	struct ao2_iterator it;
	struct ast_format *tmp;

	it = ao2_iterator_init(cap->formats, cap->nolock ? AO2_ITERATOR_DONTLOCK : 0);
	while ((tmp = ao2_iterator_next(&it))) {
		res |= ast_format_to_old_bitfield(tmp);
		ao2_ref(tmp, -1);
	}
	ao2_iterator_destroy(&it);
	return res;
}

void ast_format_cap_from_old_bitfield(struct ast_format_cap *dst, uint64_t src)
{
	uint64_t tmp = 0;
	int x;
	struct ast_format tmp_format = { 0, };

	ast_format_cap_remove_all(dst);
	for (x = 0; x < 64; x++) {
		tmp = (1ULL << x);
		if (tmp & src) {
			ast_format_cap_add(dst, ast_format_from_old_bitfield(&tmp_format, tmp));
		}
	}
}
