#define LOG_TAG "libndvoice"
#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 <cutils/log.h>

#include "libndvoice.h"
#include "hidraw.h"
#include "protocol.h"
#include "decoder.h"
#include "adpcm.h"
#include "SonyBtRcDet.h" /* PF18MTKCR-31 */

#define NDVOICE_VERSION "1.0.17010301"

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

static void audioAddWaveHeader ( FILE* fout_wav  );
static struct nvs_device *gdev=NULL;
static char ghidraw_dev[9]={0};
static FILE* fout_wav = NULL;

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 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 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 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 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 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(void)
{
    struct hidraw_report_descriptor desc;
    struct nvs_output_report report;
    int ret;

    ALOGD("[libndvoice] %s invokded", __FUNCTION__);
    ALOGD("[libndvoice] %s : Version = %s\n", __FUNCTION__, NDVOICE_VERSION);

    if ( nvs_is_connected() == 0 )
    {
        ALOGE("[libndvoice] %s : nd rc not connected", __FUNCTION__);
        return NULL;
    }

    if ( gdev )
    {
        ALOGE("[libndvoice] %s : already opened", __FUNCTION__);
        nvs_close();
    }

    gdev = malloc(sizeof(struct nvs_device));
    if (!gdev)
        return NULL;

    gdev->fd = hidraw_open(ghidraw_dev);
    if (gdev->fd < 0)
    {
        ALOGD("[libndvoice] %s error : hidraw open failed", __FUNCTION__);
        goto free_dev;
    }

    ret = hidraw_get_descriptor(gdev->fd, &desc);
    if (ret < 0)
    {
        ALOGD("[libndvoice] %s error : get descriptor failed!", __FUNCTION__);
        goto close_dev;
    }

    gdev->codec = find_codec(&desc);
	if (gdev->codec) {
		gdev->decoder = gdev->codec->decoder_init();
	} else {
        ALOGD("[libndvoice] %s error : codec not found!", __FUNCTION__);
		gdev->decoder = NULL;
	}

    if (!gdev->decoder)
        goto close_dev;

    put_cmd(&report, NVS_CMD_START);
    ALOGD("[libndvoice] %s send output report : %d:%d", __FUNCTION__, NVS_OUTPUT_REPORT_ID, NVS_CMD_START);
    ret = hidraw_send_report(gdev->fd, &report, sizeof(report));
    if (ret < 0)
        goto deinit_decoder;

    if ( fout_wav == NULL )
    {
        char* outFile_wav = "/sdcard/nd_audio.wav";
        fout_wav = fopen(outFile_wav, "w");
        if ( fout_wav != NULL )
        {
            audioAddWaveHeader(fout_wav);
        }
    }

    return gdev;

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

NVS_API void nvs_close(void)
{
    struct nvs_output_report report;

    ALOGD("[libndvoice] %s invokded", __FUNCTION__);
    if ( gdev )
    {
        put_cmd(&report, NVS_CMD_STOP);
        ALOGD("[libndvoice] %s send output report : %d:%d", __FUNCTION__, NVS_OUTPUT_REPORT_ID, NVS_CMD_START);
        hidraw_send_report(gdev->fd, &report, sizeof(report));

        gdev->decoder->decoder_deinit(gdev->decoder);
        hidraw_close(gdev->fd);
        free(gdev);

        gdev = NULL;

        memset(ghidraw_dev, 0, sizeof(ghidraw_dev));
        if ( fout_wav )
        {
            fflush(fout_wav);
            fclose(fout_wav);
            fout_wav = NULL;
        }
    }
}

NVS_API int nvs_get_audio(uint8_t *buffer, int max_size)
{
    struct nvs_input_report report;
    int size;
    int ret_size;

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

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

    ret_size = gdev->decoder->decode_pkt(gdev->decoder, report.audio,
                    size - sizeof(report.report_id),
                    (int16_t *)buffer);

    if ( ret_size > max_size )
    {
        ALOGE("[libndvoice] %s : buffer over flow! (%d:%d)", __FUNCTION__, ret_size, max_size);
    }

    if ( ret_size > 0 )
    {
        if ( fout_wav != NULL )
        {
            fwrite(buffer, 1, ret_size, fout_wav);
        }
    }

    return ret_size;
}

NVS_API int nvs_get_fd()
{
    if ( gdev )
        return gdev->fd;
    else
        return -1;
}

#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 void nvs_print_hidraw_info(void)
{
    int index=0;
    char hidraw_dev[10]={0};
    char hidraw_dev_name[256]={0};
    struct hidraw_report_descriptor desc;
    struct hidraw_devinfo devinfo;
    int hidraw_fd;

    for ( index = 0 ; index < 64 ; index++)
    {

        memset(hidraw_dev_name, 0, sizeof(hidraw_dev));
        memset(hidraw_dev, 0, sizeof(hidraw_dev));
        if ( index >= 0 && index <= 9 )
            snprintf(hidraw_dev, 8, "hidraw%d", index);
        else if ( index >= 10 )
            snprintf(hidraw_dev, 9, "hidraw%d", index);

        hidraw_fd = hidraw_open(hidraw_dev);
        if (hidraw_fd < 0)
        {
            continue;
        }

        if ( hidraw_get_name(hidraw_fd, hidraw_dev_name, sizeof(hidraw_dev_name)) == 0 )
        {
            if ( hidraw_get_descriptor(hidraw_fd, &desc) == 0 )
            {
                if ( hidraw_get_devinfo(hidraw_fd, &devinfo) == 0 )
                {
                    ALOGD("[libndvoice] %s : (%s) name = %s\n", __FUNCTION__, hidraw_dev, hidraw_dev_name);
                    ALOGD("[libndvoice] %s : (%s) descriptor size = %d\n", __FUNCTION__, hidraw_dev, desc.size);
                    ALOGD("[libndvoice] %s : (%s) bustype = %s, vendor = 0x%x, product = 0x%x\n", __FUNCTION__, hidraw_dev, hidraw_bus_str(devinfo.bustype), devinfo.vendor, devinfo.product);
                }
                else
                {
                    ALOGD("[libndvoice] %s : (%s) get device info failed! \n", __FUNCTION__, hidraw_dev);
                }
            }
            else
            {
                ALOGD("[libndvoice] %s : (%s) get descriptor failed! \n", __FUNCTION__, hidraw_dev);
            }
        }
        else
        {
            ALOGD("[libndvoice] %s : (%s) get name failed! \n", __FUNCTION__, hidraw_dev);
        }
        ALOGD(" ");
        hidraw_close(hidraw_fd);
    }
    return;
}

NVS_API int nvs_is_connected(void)
{
    int index=0;
    char hidraw_dev[10]={0};
    char hidraw_dev_name[256]={0};
    struct hidraw_report_descriptor desc;
    struct hidraw_devinfo devinfo;
    int hidraw_fd;

    ALOGD("[libndvoice] %s invokded", __FUNCTION__);
    nvs_print_hidraw_info();

    for ( index = 0 ; index < 64 ; index++)
    {

        memset(hidraw_dev_name, 0, sizeof(hidraw_dev));
        memset(hidraw_dev, 0, sizeof(hidraw_dev));
        if ( index >= 0 && index <= 9 )
            snprintf(hidraw_dev, 8, "hidraw%d", index);
        else if ( index >= 10 )
            snprintf(hidraw_dev, 9, "hidraw%d", index);

        hidraw_fd = hidraw_open(hidraw_dev);
        if (hidraw_fd < 0)
        {
            continue;
        }

        if ( hidraw_get_name(hidraw_fd, hidraw_dev_name, sizeof(hidraw_dev_name)) == 0 )
        {
            if ( hidraw_get_descriptor(hidraw_fd, &desc) == 0 )
            {
                if ( hidraw_get_devinfo(hidraw_fd, &devinfo) == 0 )
                {
                    if ( sonybtrc_is_connected(hidraw_dev_name, devinfo.vendor, devinfo.product) == 1 ) /* PF18MTKCR-31 */
                    {
                        ALOGD("[libndvoice] %s : nd rc is connected as %s\n", __FUNCTION__, hidraw_dev);
                        memcpy(ghidraw_dev, hidraw_dev, sizeof(ghidraw_dev));
                        hidraw_close(hidraw_fd);
                        return 1;
                    }
                }
            }
        }
        hidraw_close(hidraw_fd);
    }
    ALOGD("[libndvoice] %s : nd rc is NOT connected\n", __FUNCTION__);
    return 0;
}

static void audioAddWaveHeader ( FILE* fout_wav  )
{
    //Initialize directly subchunksize1
    unsigned char Header[100] = { 0x52, 0x49, 0x46, 0x46, 0xFF, 0xFF, 0xFF, 0xFF, 0x57, 0x41, 0x56, 0x45};


    //subchunksize2
    Header[12] =0x66;
    Header[13] =0x6d;
    Header[14] =0x74;
    Header[15] =0x20;

    Header[16] =16;
    Header[17] =0;
    Header[18] =0;
    Header[19] =0;

    Header[20] =1;
    Header[21] =0;

    Header[22] =1;
    Header[23] =0;

    Header[24] =0x80;
    Header[25] =0x3E;
    Header[26] =0;
    Header[27] =0;

    Header[28] =0x00;
    Header[29] =0x7D;
    Header[30] =0;
    Header[31] =0;

    Header[32] =2;
    Header[33] =0;

    Header[34] =16;
    Header[35] =0;

    Header[36] =0x64;
    Header[37] =0x61;
    Header[38] =0x74;
    Header[39] =0x61;

    Header[40] =0xFF;
    Header[41] =0xFF;
    Header[42] =0xFF;
    Header[43] =0xFF;


    fwrite(Header, sizeof(char), 44, fout_wav);
    ALOGD("[lindtvoice] ===> Add Wav Header <=== \n");
}

