/* 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 <sys/socket.h>
#include <syslog.h>

#include "cras_audio_area.h"
#include "cras_hfp_iodev.h"
#include "cras_hfp_info.h"
#include "cras_hfp_slc.h"
#include "cras_iodev.h"
#include "cras_system_state.h"
#include "cras_util.h"
#include "sfh.h"
#include "utlist.h"


struct hfp_io {
	struct cras_iodev base;
	struct cras_bt_device *device;
	struct hfp_slc_handle *slc;
	struct hfp_info *info;
};

static int update_supported_formats(struct cras_iodev *iodev)
{
	// 16 bit, mono, 8kHz
	iodev->format->format = SND_PCM_FORMAT_S16_LE;

	free(iodev->supported_rates);
	iodev->supported_rates = (size_t *)malloc(2 * sizeof(size_t));
	iodev->supported_rates[0] = 8000;
	iodev->supported_rates[1] = 0;

	free(iodev->supported_channel_counts);
	iodev->supported_channel_counts = (size_t *)malloc(2 * sizeof(size_t));
	iodev->supported_channel_counts[0] = 1;
	iodev->supported_channel_counts[1] = 0;

	free(iodev->supported_formats);
	iodev->supported_formats =
		(snd_pcm_format_t *)malloc(2 * sizeof(snd_pcm_format_t));
	iodev->supported_formats[0] = SND_PCM_FORMAT_S16_LE;
	iodev->supported_formats[1] = 0;

	return 0;
}

static int frames_queued(const struct cras_iodev *iodev,
			 struct timespec *tstamp)
{
	struct hfp_io *hfpio = (struct hfp_io *)iodev;

	if (!hfp_info_running(hfpio->info))
		return -1;

	/* Do not enable timestamp mechanism on HFP device because last time
	 * stamp might be a long time ago and it is not really useful. */
	clock_gettime(CLOCK_MONOTONIC_RAW, tstamp);
	return hfp_buf_queued(hfpio->info, iodev);
}

/* Modify the hfpio's buffer_size when the SCO packet size has changed. */
static void hfp_packet_size_changed(void *data)
{
	struct hfp_io *hfpio = (struct hfp_io *)data;
	struct cras_iodev *iodev = &hfpio->base;

	if (!cras_iodev_is_open(iodev))
		return;
	iodev->buffer_size = hfp_buf_size(hfpio->info, iodev);
	cras_bt_device_iodev_buffer_size_changed(hfpio->device);
}

static int open_dev(struct cras_iodev *iodev)
{
	struct hfp_io *hfpio = (struct hfp_io *)iodev;
	int sk, err, mtu;

	/* Assert format is set before opening device. */
	if (iodev->format == NULL)
		return -EINVAL;
	iodev->format->format = SND_PCM_FORMAT_S16_LE;
	cras_iodev_init_audio_area(iodev, iodev->format->num_channels);

	if (hfp_info_running(hfpio->info))
		goto add_dev;

	sk = cras_bt_device_sco_connect(hfpio->device);
	if (sk < 0)
		goto error;

	mtu = cras_bt_device_sco_mtu(hfpio->device, sk);

	/* Start hfp_info */
	err = hfp_info_start(sk, mtu, hfpio->info);
	if (err)
		goto error;

add_dev:
	hfp_info_add_iodev(hfpio->info, iodev);
	hfp_set_call_status(hfpio->slc, 1);

	iodev->buffer_size = hfp_buf_size(hfpio->info, iodev);

	return 0;
error:
	syslog(LOG_ERR, "Failed to open HFP iodev");
	return -1;
}

static int close_dev(struct cras_iodev *iodev)
{
	struct hfp_io *hfpio = (struct hfp_io *)iodev;

	hfp_info_rm_iodev(hfpio->info, iodev);
	if (hfp_info_running(hfpio->info) && !hfp_info_has_iodev(hfpio->info)) {
		hfp_info_stop(hfpio->info);
		hfp_set_call_status(hfpio->slc, 0);
	}

	cras_iodev_free_format(iodev);
	cras_iodev_free_audio_area(iodev);
	return 0;
}

static void set_hfp_volume(struct cras_iodev *iodev)
{
	size_t volume;
	struct hfp_io *hfpio = (struct hfp_io *)iodev;

	volume = cras_system_get_volume();
	if (iodev->active_node)
		volume = cras_iodev_adjust_node_volume(iodev->active_node, volume);

	hfp_event_speaker_gain(hfpio->slc, volume);
}

static int delay_frames(const struct cras_iodev *iodev)
{
	struct timespec tstamp;

	return frames_queued(iodev, &tstamp);
}

static int get_buffer(struct cras_iodev *iodev,
		      struct cras_audio_area **area,
		      unsigned *frames)
{
	struct hfp_io *hfpio = (struct hfp_io *)iodev;
	uint8_t *dst = NULL;

	if (!hfp_info_running(hfpio->info))
		return -1;

	hfp_buf_acquire(hfpio->info, iodev, &dst, frames);

	iodev->area->frames = *frames;
	/* HFP is mono only. */
	iodev->area->channels[0].step_bytes =
		cras_get_format_bytes(iodev->format);
	iodev->area->channels[0].buf = dst;

	*area = iodev->area;
	return 0;
}

static int put_buffer(struct cras_iodev *iodev, unsigned nwritten)
{
	struct hfp_io *hfpio = (struct hfp_io *)iodev;

	if (!hfp_info_running(hfpio->info))
		return -1;

	hfp_buf_release(hfpio->info, iodev, nwritten);
	return 0;
}

static int flush_buffer(struct cras_iodev *iodev)
{
	struct hfp_io *hfpio = (struct hfp_io *)iodev;
	unsigned nframes;

	if (iodev->direction == CRAS_STREAM_INPUT) {
		nframes = hfp_buf_queued(hfpio->info, iodev);
		hfp_buf_release(hfpio->info, iodev, nframes);
	}
	return 0;
}

static void update_active_node(struct cras_iodev *iodev, unsigned node_idx,
			       unsigned dev_enabled)
{
}

void hfp_free_resources(struct hfp_io *hfpio)
{
	struct cras_ionode *node;
	node = hfpio->base.active_node;
	if (node) {
		cras_iodev_rm_node(&hfpio->base, node);
		free(node);
	}
	free(hfpio->base.supported_channel_counts);
	free(hfpio->base.supported_rates);
}

struct cras_iodev *hfp_iodev_create(
		enum CRAS_STREAM_DIRECTION dir,
		struct cras_bt_device *device,
		struct hfp_slc_handle *slc,
		enum cras_bt_device_profile profile,
		struct hfp_info *info)
{
	struct hfp_io *hfpio;
	struct cras_iodev *iodev;
	struct cras_ionode *node;
	const char *name;

	hfpio = (struct hfp_io *)calloc(1, sizeof(*hfpio));
	if (!hfpio)
		goto error;

	iodev = &hfpio->base;
	iodev->direction = dir;

	hfpio->device = device;
	hfpio->slc = slc;

	/* Set iodev's name to device readable name or the address. */
	name = cras_bt_device_name(device);
	if (!name)
		name = cras_bt_device_object_path(device);

	snprintf(iodev->info.name, sizeof(iodev->info.name), "%s", name);
	iodev->info.name[ARRAY_SIZE(iodev->info.name) - 1] = 0;
	iodev->info.stable_id = SuperFastHash(
			cras_bt_device_object_path(device),
			strlen(cras_bt_device_object_path(device)),
			strlen(cras_bt_device_object_path(device)));
	iodev->info.stable_id_new = iodev->info.stable_id;

	iodev->open_dev= open_dev;
	iodev->frames_queued = frames_queued;
	iodev->delay_frames = delay_frames;
	iodev->get_buffer = get_buffer;
	iodev->put_buffer = put_buffer;
	iodev->flush_buffer = flush_buffer;
	iodev->close_dev = close_dev;
	iodev->update_supported_formats = update_supported_formats;
	iodev->update_active_node = update_active_node;
	iodev->set_volume = set_hfp_volume;

	node = (struct cras_ionode *)calloc(1, sizeof(*node));
	node->dev = iodev;
	strcpy(node->name, iodev->info.name);

	node->plugged = 1;
	node->type = CRAS_NODE_TYPE_BLUETOOTH;
	node->volume = 100;
	gettimeofday(&node->plugged_time, NULL);

	cras_bt_device_append_iodev(device, iodev, profile);
	cras_iodev_add_node(iodev, node);
	cras_iodev_set_active_node(iodev, node);

	hfpio->info = info;
	hfp_register_packet_size_changed_callback(info,
						  hfp_packet_size_changed,
						  hfpio);

	return iodev;

error:
	if (hfpio) {
		hfp_free_resources(hfpio);
		free(hfpio);
	}
	return NULL;
}

void hfp_iodev_destroy(struct cras_iodev *iodev)
{
	struct hfp_io *hfpio = (struct hfp_io *)iodev;

	cras_bt_device_rm_iodev(hfpio->device, iodev);
	hfp_unregister_packet_size_changed_callback(hfpio->info, hfpio);
	hfp_free_resources(hfpio);
	free(hfpio);
}