/* 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 <netinet/in.h>
#include <sbc/sbc.h>
#include <syslog.h>

#include "cras_a2dp_info.h"
#include "cras_sbc_codec.h"
#include "cras_types.h"
#include "rtp.h"

int init_a2dp(struct a2dp_info *a2dp, a2dp_sbc_t *sbc)
{
	uint8_t frequency = 0, mode = 0, subbands = 0, allocation, blocks = 0,
		bitpool;

	if (sbc->frequency & SBC_SAMPLING_FREQ_48000)
		frequency = SBC_FREQ_48000;
	else if (sbc->frequency & SBC_SAMPLING_FREQ_44100)
		frequency = SBC_FREQ_44100;
	else if (sbc->frequency & SBC_SAMPLING_FREQ_32000)
		frequency = SBC_FREQ_32000;
	else if (sbc->frequency & SBC_SAMPLING_FREQ_16000)
		frequency = SBC_FREQ_16000;

	if (sbc->channel_mode & SBC_CHANNEL_MODE_JOINT_STEREO)
		mode = SBC_MODE_JOINT_STEREO;
	else if (sbc->channel_mode & SBC_CHANNEL_MODE_STEREO)
		mode = SBC_MODE_STEREO;
	else if (sbc->channel_mode & SBC_CHANNEL_MODE_DUAL_CHANNEL)
		mode = SBC_MODE_DUAL_CHANNEL;
	else if (sbc->channel_mode & SBC_CHANNEL_MODE_MONO)
		mode = SBC_MODE_MONO;

	if (sbc->allocation_method & SBC_ALLOCATION_LOUDNESS)
		allocation = SBC_AM_LOUDNESS;
	else
		allocation = SBC_AM_SNR;

	switch (sbc->subbands) {
	case SBC_SUBBANDS_4:
		subbands = SBC_SB_4;
		break;
	case SBC_SUBBANDS_8:
		subbands = SBC_SB_8;
		break;
	}

	switch (sbc->block_length) {
	case SBC_BLOCK_LENGTH_4:
		blocks = SBC_BLK_4;
		break;
	case SBC_BLOCK_LENGTH_8:
		blocks = SBC_BLK_8;
		break;
	case SBC_BLOCK_LENGTH_12:
		blocks = SBC_BLK_12;
		break;
	case SBC_BLOCK_LENGTH_16:
		blocks = SBC_BLK_16;
		break;
	}

	bitpool = sbc->max_bitpool;

	a2dp->codec = cras_sbc_codec_create(frequency, mode, subbands,
					    allocation, blocks, bitpool);
	if (!a2dp->codec)
		return -1;

	/* SBC info */
	a2dp->codesize = cras_sbc_get_codesize(a2dp->codec);
	a2dp->frame_length = cras_sbc_get_frame_length(a2dp->codec);

	a2dp->a2dp_buf_used = sizeof(struct rtp_header)
			+ sizeof(struct rtp_payload);
	a2dp->frame_count = 0;
	a2dp->seq_num = 0;
	a2dp->samples = 0;

	return 0;
}

void destroy_a2dp(struct a2dp_info *a2dp)
{
	cras_sbc_codec_destroy(a2dp->codec);
}

int a2dp_codesize(struct a2dp_info *a2dp)
{
	return a2dp->codesize;
}

int a2dp_block_size(struct a2dp_info *a2dp, int a2dp_bytes)
{
	return a2dp_bytes / a2dp->frame_length * a2dp->codesize;
}

int a2dp_queued_frames(const struct a2dp_info *a2dp)
{
	return a2dp->samples;
}

void a2dp_drain(struct a2dp_info *a2dp)
{
	a2dp->a2dp_buf_used = sizeof(struct rtp_header)
			+ sizeof(struct rtp_payload);
	a2dp->samples = 0;
	a2dp->seq_num = 0;
	a2dp->frame_count = 0;
}

static int avdtp_write(int stream_fd, struct a2dp_info *a2dp)
{
	int err, samples;
	struct rtp_header *header;
	struct rtp_payload *payload;

	header = (struct rtp_header *)a2dp->a2dp_buf;
	payload = (struct rtp_payload *)(a2dp->a2dp_buf + sizeof(*header));
	memset(a2dp->a2dp_buf, 0, sizeof(*header) + sizeof(*payload));

	payload->frame_count = a2dp->frame_count;
	header->v = 2;
	header->pt = 1;
	header->sequence_number = htons(a2dp->seq_num);
	header->timestamp = htonl(a2dp->nsamples);
	header->ssrc = htonl(1);

	err = send(stream_fd, a2dp->a2dp_buf, a2dp->a2dp_buf_used,
		   MSG_DONTWAIT);
	if (err < 0)
		return -errno;

	/* Returns the number of samples in frame. */
	samples = a2dp->samples;

	/* Reset some data */
	a2dp->a2dp_buf_used = sizeof(*header) + sizeof(*payload);
	a2dp->frame_count = 0;
	a2dp->samples = 0;
	a2dp->seq_num++;

	return samples;
}

int a2dp_encode(struct a2dp_info *a2dp, const void *pcm_buf, int pcm_buf_size,
		int format_bytes, size_t link_mtu)
{
	int processed;
	size_t out_encoded;

	if (link_mtu > A2DP_BUF_SIZE_BYTES)
		link_mtu = A2DP_BUF_SIZE_BYTES;
	if (link_mtu == a2dp->a2dp_buf_used)
		return 0;

	processed = a2dp->codec->encode(a2dp->codec, pcm_buf, pcm_buf_size,
					a2dp->a2dp_buf + a2dp->a2dp_buf_used,
					link_mtu - a2dp->a2dp_buf_used,
					&out_encoded);
	if (processed < 0) {
		syslog(LOG_ERR, "a2dp encode error %d", processed);
		return processed;
	}

	if (a2dp->codesize > 0)
		a2dp->frame_count += processed / a2dp->codesize;
	a2dp->a2dp_buf_used += out_encoded;

	a2dp->samples += processed / format_bytes;
	a2dp->nsamples += processed / format_bytes;

	return processed;
}

int a2dp_write(struct a2dp_info *a2dp, int stream_fd, size_t link_mtu)
{
	/* Do avdtp write when the max number of SBC frames is reached. */
	if (a2dp->a2dp_buf_used + a2dp->frame_length >
	    link_mtu - sizeof(struct rtp_header) - sizeof(struct rtp_payload))
		return avdtp_write(stream_fd, a2dp);

	return 0;
}