/* Copyright (c) 2013 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 <errno.h>
#include <sbc/sbc.h>
#include <stdlib.h>

#include "cras_sbc_codec.h"

/* SBC library encodes one PCM input block to one SBC output block. This
 * structure holds related info about the SBC codec.
 * Members:
 *    sbc - The main structure for SBC codec.
 *    codesize - The size of one PCM input block in bytes.
 *    frame_length - The size of one SBC output block in bytes.
 */
struct cras_sbc_data {
	sbc_t sbc;
	unsigned int codesize;
	unsigned int frame_length;
};

int cras_sbc_decode(struct cras_audio_codec *codec, const void *input,
		    size_t input_len, void *output, size_t output_len,
		    size_t *count) {
	struct cras_sbc_data *data = (struct cras_sbc_data *)codec->priv_data;
	size_t written;
	ssize_t decoded;
	int processed = 0;
	int result = 0;

	/* Proceed decode when there is buffer left in input and room in
	 * output.
	 */
	while (input_len > processed && output_len > result) {
		decoded = sbc_decode(&data->sbc,
				     input + processed,
				     input_len - processed,
				     output + result,
				     output_len - result,
				     &written);
		if (decoded <= 0)
			break;

		processed += decoded;
		result += written;
	}
	*count = result;
	return processed;
}

int cras_sbc_encode(struct cras_audio_codec *codec, const void *input,
		    size_t input_len, void *output, size_t output_len,
		    size_t *count) {
	struct cras_sbc_data *data = (struct cras_sbc_data *)codec->priv_data;
	ssize_t written, encoded;
	int processed = 0, result = 0;

	/* Proceed encode when input buffer has at least one input block and
	 * there is still room in output buffer.
	 */
	while (input_len - processed >= data->codesize &&
			output_len >= result) {
		encoded = sbc_encode(&data->sbc,
				     input + processed,
				     data->codesize,
				     output + result,
				     output_len - result,
				     &written);
		if (encoded == -ENOSPC)
			break;
		else if (encoded < 0)
			return encoded;

		processed += encoded;
		result += written;
	}
	*count = result;
	return processed;
}

int cras_sbc_get_codesize(struct cras_audio_codec *codec)
{
	struct cras_sbc_data *data = (struct cras_sbc_data *)codec->priv_data;
	return data->codesize;
}

int cras_sbc_get_frame_length(struct cras_audio_codec *codec)
{
	struct cras_sbc_data *data = (struct cras_sbc_data *)codec->priv_data;
	return data->frame_length;
}

struct cras_audio_codec *cras_sbc_codec_create(uint8_t freq,
		   uint8_t mode, uint8_t subbands, uint8_t alloc,
		   uint8_t blocks, uint8_t bitpool) {
	struct cras_audio_codec *codec;
	struct cras_sbc_data *data;

	codec = (struct cras_audio_codec *)calloc(1, sizeof(*codec));
	if (!codec)
		return NULL;

	codec->priv_data = (struct cras_sbc_data *)calloc(1,
			sizeof(struct cras_sbc_data));
	if (!codec->priv_data)
		goto create_error;

	data = (struct cras_sbc_data *)codec->priv_data;
	sbc_init(&data->sbc, 0L);
	data->sbc.endian = SBC_LE;
	data->sbc.frequency = freq;
	data->sbc.mode = mode;
	data->sbc.subbands = subbands;
	data->sbc.allocation = alloc;
	data->sbc.blocks = blocks;
	data->sbc.bitpool = bitpool;
	data->codesize = sbc_get_codesize(&data->sbc);
	data->frame_length = sbc_get_frame_length(&data->sbc);

	codec->decode = cras_sbc_decode;
	codec->encode = cras_sbc_encode;
	return codec;

create_error:
	free(codec);
	return NULL;
}

void cras_sbc_codec_destroy(struct cras_audio_codec *codec)
{
	sbc_finish(&((struct cras_sbc_data *)codec->priv_data)->sbc);
	free(codec->priv_data);
	free(codec);
}