#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <adpcm.h>

#include "nvs.h"
#include "hidraw.h"
#include "protocol.h"
#include "decoder.h"

#define NVS_API __attribute__((visibility ("default")))

struct nvs_codec {
	unsigned int		sampling_rate;
	unsigned int		report_id;

	const uint8_t		*descriptor;
	unsigned int		descriptor_size;

	struct nvs_decoder *	(*decoder_init)(void);
};

struct nvs_device {
	const struct nvs_codec	*codec;
	struct nvs_decoder	*decoder;
	int			fd;
};

static const uint8_t bv32_desc[] = {
	0x06, 0x00, 0xFF, 0x09, 0x00, 0xA1, 0x01, 0x85, 0x01, 0x15, 0x00, 0x25,
	0xFF, 0x75, 0x08, 0x95, 0x14, 0x09, 0x00, 0x81, 0x02, 0x95, 0x01, 0x75,
	0x08, 0x09, 0x00, 0x91, 0x02, 0xC0 };

static const uint8_t adpcm_h256_desc[] = {
	0x06, 0x00, 0xFF, 0x09, 0x00, 0xA1, 0x01, 0x85, 0x02, 0x15, 0x00, 0x25,
	0xFF, 0x75, 0x08, 0x95, 0x14, 0x09, 0x00, 0x81, 0x02, 0x85, 0x01, 0x95,
	0x01, 0x75, 0x08, 0x09, 0x00, 0x91, 0x02, 0xC0 };

static const uint8_t adpcm_h128_desc[] = {
	0x06, 0x00, 0xFF, 0x09, 0x01, 0xA1, 0x01, 0x85, 0x02, 0x15, 0x00, 0x25,
	0xFF, 0x75, 0x08, 0x95, 0x14, 0x09, 0x00, 0x81, 0x02, 0x85, 0x01, 0x95,
	0x01, 0x75, 0x08, 0x09, 0x00, 0x91, 0x02, 0xC0 };

static const uint8_t adpcm_h64_desc[] = {
	0x06, 0x00, 0xFF, 0x09, 0x02, 0xA1, 0x01, 0x85, 0x02, 0x15, 0x00, 0x25,
	0xFF, 0x75, 0x08, 0x95, 0x14, 0x09, 0x00, 0x81, 0x02, 0x85, 0x01, 0x95,
	0x01, 0x75, 0x08, 0x09, 0x00, 0x91, 0x02, 0xC0 };

static const uint8_t adpcm_h256_desc_rmf_tx20xu[] = {
	0x06, 0x00, 0xFF, 0x09, 0x04, 0xA1, 0x01, 0x85, 0x02, 0x15, 0x00, 0x25,
	0xFF, 0x75, 0x08, 0x95, 0x14, 0x09, 0x00, 0x81, 0x02, 0x85, 0x01, 0x95,
	0x01, 0x75, 0x08, 0x09, 0x00, 0x91, 0x02, 0xC0 };

static const uint8_t opus_desc[] = {
	0x06, 0x00, 0xFF, 0x09, 0x00, 0xA1, 0x01, 0x85, 0x03, 0x15, 0x00, 0x25,
	0xFF, 0x75, 0x08, 0x95, 0x14, 0x09, 0x00, 0x81, 0x02, 0x85, 0x01, 0x95,
	0x01, 0x75, 0x08, 0x09, 0x00, 0x91, 0x02, 0xC0 };

static struct nvs_decoder *adpcm_h256_init(void)
{
	return decoder_adpcm_init(ADPCM_HEADER_SIZE + 256);
}

static struct nvs_decoder *adpcm_h128_init(void)
{
	return decoder_adpcm_init(ADPCM_HEADER_SIZE + 128);
}

static struct nvs_decoder *adpcm_h64_init(void)
{
	return decoder_adpcm_init(ADPCM_HEADER_SIZE + 64);
}

static struct nvs_decoder *opus_init(void)
{
	return decoder_opus_init(16000);
}

static const struct nvs_codec nvs_codecs[] = {
	{
		/* BV32 */
		.sampling_rate		= 16125,
		.report_id		= 1,
		.descriptor		= bv32_desc,
		.descriptor_size	= sizeof(bv32_desc),
		.decoder_init		= decoder_bv32_init,
	}, {
		/* ADPCM H256 */
		.sampling_rate		= 16000,
		.report_id		= 2,
		.descriptor		= adpcm_h256_desc,
		.descriptor_size	= sizeof(adpcm_h256_desc),
		.decoder_init		= adpcm_h256_init,
	}, {
		/* ADPCM H128 */
		.sampling_rate		= 16000,
		.report_id		= 2,
		.descriptor		= adpcm_h128_desc,
		.descriptor_size	= sizeof(adpcm_h128_desc),
		.decoder_init		= adpcm_h128_init,
	}, {
		/* ADPCM H64 */
		.sampling_rate		= 16000,
		.report_id		= 2,
		.descriptor		= adpcm_h64_desc,
		.descriptor_size	= sizeof(adpcm_h64_desc),
		.decoder_init		= adpcm_h64_init,
	}, {
		/* ADPCM H256 for RMF-TX20XU */
		.sampling_rate		= 16000,
		.report_id		= 2,
		.descriptor		= adpcm_h256_desc_rmf_tx20xu,
		.descriptor_size	= sizeof(adpcm_h256_desc_rmf_tx20xu),
		.decoder_init		= adpcm_h256_init,
	}, {
		/* OPUS */
		.sampling_rate		= 16125,
		.report_id		= 3,
		.descriptor		= opus_desc,
		.descriptor_size	= sizeof(opus_desc),
		.decoder_init		= opus_init,
	}
};

static const struct nvs_codec *find_codec(struct hidraw_report_descriptor *desc)
{
	const struct nvs_codec *codec = NULL;
	unsigned int i;

	for (i = 0; i < (sizeof(nvs_codecs) / sizeof(nvs_codecs[0])); i++) {
		const struct nvs_codec *c = &nvs_codecs[i];

		if (desc->size < c->descriptor_size)
			continue;

		if (memcmp(desc->value, c->descriptor, c->descriptor_size) == 0) {
			codec = c;
			break;
		}
	}

	return codec;
}

NVS_API struct nvs_device *nvs_open(const char *dev_name)
{
	struct hidraw_report_descriptor desc;
	struct nvs_output_report report;
	struct nvs_device *dev;
	int ret;

	dev = malloc(sizeof(struct nvs_device));
	if (!dev)
		return NULL;
	
	dev->fd = hidraw_open(dev_name);
	if (dev->fd < 0)
		goto free_dev;

	ret = hidraw_get_descriptor(dev->fd, &desc);
	if (ret < 0)
		goto close_dev;

	dev->codec = find_codec(&desc);
	if (dev->codec) {
		dev->decoder = dev->codec->decoder_init();
	} else {
		dev->decoder = NULL;
	}

	if (!dev->decoder)
		goto close_dev;

	put_cmd(&report, NVS_CMD_START);
	ret = hidraw_send_report(dev->fd, &report, sizeof(report));
	if (ret < 0)
		goto deinit_decoder;

	return dev;

deinit_decoder:
	dev->decoder->decoder_deinit(dev->decoder);
close_dev:
	hidraw_close(dev->fd);
free_dev:
	free(dev);
	return NULL;
}

NVS_API void nvs_close(struct nvs_device *dev)
{
	struct nvs_output_report report;

	put_cmd(&report, NVS_CMD_STOP);
	hidraw_send_report(dev->fd, &report, sizeof(report));

	dev->decoder->decoder_deinit(dev->decoder);
	hidraw_close(dev->fd);
	free(dev);
}

NVS_API int nvs_get_audio(struct nvs_device *dev, uint8_t *buffer)
{
	struct nvs_input_report report;
	int size;

	size = hidraw_get_report(dev->fd, &report, sizeof(report));
	if (size < 0)
		return size;

	if (dev->codec->report_id != report.report_id)
		return -EAGAIN;

	return dev->decoder->decode_pkt(dev->decoder, report.audio,
					size - sizeof(report.report_id),
					(int16_t *)buffer);
}

NVS_API int nvs_get_fd(struct nvs_device *dev)
{
	return dev->fd;
}

#define DESCPATH_MAX_SIZE 64

static int read_descriptor_sysfs(const char *dev_name,
				 struct hidraw_report_descriptor *desc)
{
	char desc_path[DESCPATH_MAX_SIZE];
	int fd, nread;

	snprintf(desc_path, DESCPATH_MAX_SIZE,
		 "/sys/class/hidraw/%s/device/report_descriptor",
		 dev_name);

	fd = open(desc_path, O_RDONLY);
	if (fd < 0)
		return fd;

	desc->size = 0;
	while (1) {
		nread = read(fd, &desc->value[desc->size],
			     HID_MAX_DESCRIPTOR_SIZE - desc->size);
		if (nread <= 0)
			break;
		desc->size += nread;
	}

	close(fd);

	return nread;
}

NVS_API int nvs_check_descriptor(const char *dev_name, unsigned int *sampling_rate)
{
	struct hidraw_report_descriptor desc;
	const struct nvs_codec *codec;
	int ret;

	ret = read_descriptor_sysfs(dev_name, &desc);
	if (ret < 0)
		return ret;

	codec = find_codec(&desc);
	if (!codec)
		return 0;

	if (sampling_rate)
		*sampling_rate = codec->sampling_rate;

	return 1;
}
