/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include <syscfg/syscfg.h>

#if MYNEWT_VAL(AUDIO_USB)
#include <assert.h>
#include <bsp/bsp.h>
#include <string.h>
#include <tusb.h>
#include <device/usbd.h>
#include <class/audio/audio.h>
#include <os/util.h>
#include <usb_audio.h>

#define USBD_PRODUCT_RELEASE_NUMBER MYNEWT_VAL(USBD_PRODUCT_RELEASE_NUMBER)

#ifndef CONFIG_NUM
#define CONFIG_NUM 1
#endif

typedef enum {
    USB_STRING_DESCRIPTOR_LANG          = 0,
    USB_STRING_DESCRIPTOR_MANUFACTURER  = 1,
    USB_STRING_DESCRIPTOR_PRODUCT       = 2,
    USB_STRING_DESCRIPTOR_INTERFACE     = 3,
    USB_STRING_DESCRIPTOR_CDC           = 4,
    USB_STRING_DESCRIPTOR_SERIAL        = 16,
    USB_STRING_DESCRIPTOR_MICROSOFT_OS  = 0xEE,
} usb_string_descriptor_ix_t;

#define CDC_IF_STR_IX (MYNEWT_VAL(USBD_CDC_DESCRIPTOR_STRING) == NULL ? 0 : 4)

const tusb_desc_device_t desc_device = {
    .bLength = sizeof(tusb_desc_device_t),
    .bDescriptorType = TUSB_DESC_DEVICE,
    .bcdUSB = 0x0200,

    .bDeviceClass = 0x00,
    .bDeviceSubClass = 0x00,
    .bDeviceProtocol = 0x00,

    .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,

    .idVendor = MYNEWT_VAL(USBD_VID),
    .idProduct = MYNEWT_VAL(USBD_PID),
    .bcdDevice = USBD_PRODUCT_RELEASE_NUMBER,

    .iManufacturer = USB_STRING_DESCRIPTOR_MANUFACTURER,
    .iProduct = USB_STRING_DESCRIPTOR_PRODUCT,
    .iSerialNumber = USB_STRING_DESCRIPTOR_SERIAL,

    .bNumConfigurations = 0x01
};

/*
 * Invoked when received GET DEVICE DESCRIPTOR
 * Application return pointer to descriptor
 */
const uint8_t *
tud_descriptor_device_cb(void)
{
    return (const uint8_t *)&desc_device;
}

#if MYNEWT_VAL_CHOICE(MCU_TARGET, nRF5340_APP) || MYNEWT_VAL_CHOICE(MCU_TARGET, nRF52840)
#define ISO_EP  8
#else
#error MCU not supported
#endif

/*
 * Configuration Descriptor
 */

enum {
#if CFG_TUD_BTH
    ITF_NUM_BTH,
#if CFG_TUD_BTH_ISO_ALT_COUNT > 0
    ITF_NUM_BTH_VOICE,
#endif
#endif

#if CFG_TUD_CDC
    ITF_NUM_CDC,
    ITF_NUM_CDC_DATA,
#endif

#if CFG_TUD_MSC
    ITF_NUM_MSC,
#endif

#if CFG_TUD_HID
    ITF_NUM_HID,
#endif

#if CFG_TUD_AUDIO_IN
    ITF_NUM_AUDIO_AC,
    ITF_NUM_AUDIO_AS_IN,
#elif CFG_TUD_AUDIO_OUT
    ITF_NUM_AUDIO_AC,
    ITF_NUM_AUDIO_AS_OUT,
#elif CFG_TUD_AUDIO_IN_OUT
    ITF_NUM_AUDIO_AC,
    ITF_NUM_AUDIO_AS_IN,
    ITF_NUM_AUDIO_AS_OUT,
#endif

    ITF_NUM_TOTAL
};

#define CONFIG_TOTAL_LEN    (TUD_CONFIG_DESC_LEN + \
                             CFG_TUD_CDC * TUD_CDC_DESC_LEN + \
                             CFG_TUD_MSC * TUD_MSC_DESC_LEN + \
                             CFG_TUD_HID * TUD_HID_DESC_LEN + \
                             CFG_TUD_BTH * TUD_BTH_DESC_LEN + \
                             CFG_TUD_AUDIO_IN * TUD_AUDIO_MIC_ONE_CH_DESC_LEN + \
                             (1 + CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP) * CFG_TUD_AUDIO_OUT * \
                             (TUD_AUDIO_SPEAKER_MONO_DESC_LEN * (CFG_TUD_AUDIO_N_CHANNELS_RX == 1 ? 1 : 0)) + \
                             (1 - CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP) * CFG_TUD_AUDIO_OUT * \
                             (TUD_AUDIO_SPEAKER_STEREO_DESC_LEN * (CFG_TUD_AUDIO_N_CHANNELS_RX == 2 ? 1 : 0)) + \
                             0)

const uint8_t desc_configuration[] = {
    TUD_CONFIG_DESCRIPTOR(CONFIG_NUM, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP,
                          MYNEWT_VAL(USBD_CONFIGURATION_MAX_POWER)),

#if CFG_TUD_BTH
    TUD_BTH_DESCRIPTOR(ITF_NUM_BTH, BTH_IF_STR_IX, USBD_BTH_EVENT_EP, USBD_BTH_EVENT_EP_SIZE,
                       USBD_BTH_EVENT_EP_INTERVAL, USBD_BTH_DATA_IN_EP, USBD_BTH_DATA_OUT_EP, USBD_BTH_DATA_EP_SIZE,
                       0, 9, 17, 25, 33, 49),
#endif


#if CFG_TUD_CDC
    TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, CDC_IF_STR_IX, USBD_CDC_NOTIFY_EP, USBD_CDC_NOTIFY_EP_SIZE,
                       USBD_CDC_DATA_OUT_EP, USBD_CDC_DATA_IN_EP, USBD_CDC_DATA_EP_SIZE),
#endif

#if CFG_TUD_MSC
    /* TODO: MSC not handled yet */
    TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, MSC_IF_STR_IX, EPNUM_MSC_OUT, EPNUM_MSC_IN,
                       (CFG_TUSB_RHPORT0_MODE & OPT_MODE_HIGH_SPEED) ? 512 : 64),
#endif

#if CFG_TUD_HID
    TUD_HID_DESCRIPTOR(ITF_NUM_HID, HID_IF_STR_IX, HID_PROTOCOL_NONE, sizeof(desc_hid_report),
                       USBD_HID_REPORT_EP, USBD_HID_REPORT_EP_SIZE, USBD_HID_REPORT_EP_INTERVAL),
#endif

#if CFG_TUD_AUDIO_IN_OUT
#elif CFG_TUD_AUDIO_IN
    TUD_AUDIO_MIC2_DESCRIPTOR(ITF_NUM_AUDIO_AC_IN, 0, 2, 16, 0x80 | ISO_EP, 192),
#elif CFG_TUD_AUDIO_OUT && CFG_TUD_AUDIO_N_CHANNELS_RX == 2
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP == 1
    TUD_AUDIO_SPEAKER_STEREO_FB_DESCRIPTOR(ITF_NUM_AUDIO_AC, 0, CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX,
    8 * CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX, ISO_EP, CFG_TUD_AUDIO_EPSIZE_OUT, 0x80 | ISO_EP, 1),
#else
    TUD_AUDIO_SPEAKER_STEREO_DESCRIPTOR(ITF_NUM_AUDIO_AC, 0, CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX,
                                        8 * CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX, ISO_EP, CFG_TUD_AUDIO_EPSIZE_OUT),
#endif
#elif CFG_TUD_AUDIO_OUT && CFG_TUD_AUDIO_N_CHANNELS_RX == 1
    #if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
    TUD_AUDIO_SPEAKER_MONO_FB_DESCRIPTOR(ITF_NUM_AUDIO_AC, 0, 2, 16, ISO_EP, 100, 0x80 | ISO_EP),
#else
    TUD_AUDIO_SPEAKER_MONO_DESCRIPTOR(ITF_NUM_AUDIO_AC, 0, CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX,
    8 * CFG_TUD_AUDIO_N_BYTES_PER_SAMPLE_RX, ISO_EP, CFG_TUD_AUDIO_EPSIZE_OUT),
#endif
#endif
};

/**
 * Invoked when received GET CONFIGURATION DESCRIPTOR
 * Application return pointer to descriptor
 * Descriptor contents must exist long enough for transfer to complete
 */
const uint8_t *
tud_descriptor_configuration_cb(uint8_t index)
{
    (void)index;

    return desc_configuration;
}

static uint16_t desc_string[MYNEWT_VAL(USBD_STRING_DESCRIPTOR_MAX_LENGTH) + 1];

#if CFG_TUD_AUDIO

#if CFG_TUD_AUDIO_IN
const uint16_t tud_audio_desc_lengths[] = {TUD_AUDIO_MIC2_DESC_LEN};
#elif CFG_TUD_AUDIO_OUT && CFG_TUD_AUDIO_N_CHANNELS_RX == 1
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
const uint16_t tud_audio_desc_lengths[] = {TUD_AUDIO_SPEAKER_MONO_FB_DESC_LEN};
#else
const uint16_t tud_audio_desc_lengths[] = {TUD_AUDIO_SPEAKER_MONO_DESC_LEN};
#endif
#elif CFG_TUD_AUDIO_OUT && CFG_TUD_AUDIO_N_CHANNELS_RX == 2
#if CFG_TUD_AUDIO_ENABLE_FEEDBACK_EP
const uint16_t tud_audio_desc_lengths[] = {TUD_AUDIO_SPEAKER_STEREO_FB_DESC_LEN};
#else
const uint16_t tud_audio_desc_lengths[] = {TUD_AUDIO_SPEAKER_STEREO_DESC_LEN};
#endif
#endif

static uint32_t g_sample_rate = CFG_TUD_AUDIO_SAMPLE_RATE;
static usb_audio_sample_rate_cb_t sample_rate_cb;

/* Invoked when audio class specific set request received for an entity */
bool
tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request, uint8_t *pBuff)
{
    uint32_t new_sample_rate;

    (void) rhport;
    audio_control_request_t *request = (audio_control_request_t *) p_request;

    if (request->bEntityID == 2 && request->bControlSelector == AUDIO_FU_CTRL_VOLUME &&
        request->bRequest == AUDIO_CS_REQ_CUR) {
        /* Ignore value but accept request */
        return true;
    } else if (request->bEntityID == 4 && request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ &&
               request->bRequest == AUDIO_CS_REQ_CUR) {
        /* Ignore value but accept request */
        new_sample_rate = ((audio_control_cur_4_t *)(pBuff))->bCur;
        if (new_sample_rate != g_sample_rate) {
            g_sample_rate = new_sample_rate;
            if (sample_rate_cb) {
                sample_rate_cb(g_sample_rate);
            }
        }
        return true;
    } else {
        __BKPT(1);
    }
    return false;
}

bool
tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const * p_request)
{
    (void) rhport;
    audio_control_request_t *request = (audio_control_request_t *) p_request;
    if (request->bEntityID == 4 && request->bControlSelector == AUDIO_CS_CTRL_SAM_FREQ) {
        if (request->bRequest == AUDIO_CS_REQ_CUR) {
            audio_control_cur_4_t curf = {g_sample_rate};
            return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &curf, sizeof(curf));
        } else if (request->bRequest == AUDIO_CS_REQ_RANGE) {
#if 1
            audio_control_range_4_n_t(1) rangef = {
                .wNumSubRanges = 1,
                .subrange[0] = {g_sample_rate, g_sample_rate, 0},
            };
#else
            audio_control_range_4_n_t(2) rangef = {
                .wNumSubRanges = 2,
                .subrange[0] = {16000, 16000, 0},
                .subrange[1] = {48000, 48000, 0},
            };
#endif
            return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &rangef, sizeof(rangef));
        }
    } else if (request->bEntityID == 5 && request->bControlSelector == AUDIO_CX_CTRL_CONTROL) {
        if (request->bRequest == AUDIO_CS_REQ_CUR) {
            audio_control_cur_1_t cur_clk = {1};
            return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &cur_clk, sizeof(cur_clk));
        }
    } else if (request->bEntityID == 4 && request->bControlSelector == AUDIO_CS_CTRL_CLK_VALID &&
               request->bRequest == AUDIO_CS_REQ_CUR) {
        audio_control_cur_1_t cur_valid = {.bCur = 1};
        return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &cur_valid, sizeof(cur_valid));
    } else if (request->bEntityID == 2 && request->bControlSelector == AUDIO_FU_CTRL_MUTE &&
               request->bRequest == AUDIO_CS_REQ_CUR) {
        audio_control_cur_1_t mute = {.bCur = 0};
        return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &mute, sizeof(mute));
    } else if (request->bEntityID == 2 && request->bControlSelector == AUDIO_FU_CTRL_VOLUME) {
        if (request->bRequest == AUDIO_CS_REQ_RANGE) {
            audio_control_range_2_n_t(1) range_vol = {
                .wNumSubRanges = 1,
                .subrange[0] = {0, 1000, 10}
            };
            return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &range_vol, sizeof(range_vol));
        } else if (request->bRequest == AUDIO_CS_REQ_CUR) {
            audio_control_cur_2_t cur_vol = {.bCur = 1280};
            return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &cur_vol, sizeof(cur_vol));
        }
    } else {
        __BKPT(1);
    }
    return false;
}

#endif

/* Function converts ASCII string to USB descriptor format */
static uint16_t *
string_to_usb_desc_string(const char *str, uint16_t *desc, uint8_t desc_size)
{
    int i;
    int char_num = strlen(str);

    assert(char_num < desc_size);
    if (char_num >= desc_size) {
        char_num = desc_size - 1;
    }

    /* Encode length in first byte, type in second byte */
    desc[0] = tu_htole16(tu_u16(TUSB_DESC_STRING, 2 * (char_num + 1)));

    /* Copy characters 8bit to 16 bits */
    for (i = 0; i < char_num; ++i) {
        desc[1 + i] = tu_htole16(str[i]);
    }

    return desc;
}

/* LANGID string descriptors */
static const uint16_t usbd_lang_id[2] = {
    (TUSB_DESC_STRING << 8) + 4, /* Size of this descriptor */
    tu_htole16(MYNEWT_VAL(USBD_LANGID))
};

static struct {
    uint16_t major;
    uint16_t minor;
    uint16_t revision;
    uint32_t build;
} img_version = {
    1, 0, 0, 1
};

static char serial_number[11];

static uint16_t *
serial_to_usb_desc_string(uint16_t *desc, size_t size)
{
    if (serial_number[0] == 0) {
        uint64_t serial = MYNEWT_VAL(USBD_SERIAL_ID);

        snprintf(serial_number, 11, "%010" PRIu64, serial);
    }

    return string_to_usb_desc_string(serial_number, desc, size);
}

/*
 * Invoked when received GET STRING DESCRIPTOR request
 * Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
 */
const uint16_t *
tud_descriptor_string_cb(uint8_t index, uint16_t langid)
{
    const uint16_t *ret = NULL;
    char interface[100];

#if MYNEWT_VAL(USBD_WINDOWS_COMP_ID)
    if (index == USB_STRING_DESCRIPTOR_MICROSOFT_OS) {
        ret = (const uint16_t *)microsoft_os_string_descriptor;
    }
#endif
    if (index == USB_STRING_DESCRIPTOR_LANG) {
        ret = usbd_lang_id;
    } else if (index == USB_STRING_DESCRIPTOR_SERIAL) {
        ret = serial_to_usb_desc_string(desc_string, ARRAY_SIZE(desc_string));
    } else if (index == USB_STRING_DESCRIPTOR_MANUFACTURER) {
        ret = string_to_usb_desc_string(MYNEWT_VAL(USBD_VENDOR_STRING), desc_string, ARRAY_SIZE(desc_string));
    } else if (index == USB_STRING_DESCRIPTOR_PRODUCT) {
        ret = string_to_usb_desc_string(MYNEWT_VAL(USBD_PRODUCT_STRING), desc_string, ARRAY_SIZE(desc_string));
    } else if (index == USB_STRING_DESCRIPTOR_INTERFACE) {
        snprintf(interface, sizeof(interface), "%s, (%u.%u.%u.%lu)", MYNEWT_VAL(USBD_PRODUCT_STRING),
                 img_version.major, img_version.minor, img_version.revision, img_version.build);
        ret = string_to_usb_desc_string(interface, desc_string, ARRAY_SIZE(desc_string));
    }
    return ret;
}

void
usb_desc_sample_rate_set(uint32_t sample_rate)
{
    g_sample_rate = sample_rate;
}
#endif /* AUDIO_USB */
