/*
 * Copyright (c) 2022, sakumisu
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include "usbd_core.h"
#include "usbd_audio.h"

struct audio_entity_param {
    uint32_t wCur;
    uint32_t wMin;
    uint32_t wMax;
    uint32_t wRes;
};

struct usbd_audio_priv {
    struct audio_entity_info *table;
    uint8_t num;
    uint16_t uac_version;
} g_usbd_audio[CONFIG_USBDEV_MAX_BUS];

static int audio_class_endpoint_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len)
{
    uint8_t control_selector;
    uint32_t sampling_freq = 0;
    uint8_t ep;

    control_selector = HI_BYTE(setup->wValue);
    ep = LO_BYTE(setup->wIndex);

    switch (control_selector) {
        case AUDIO_EP_CONTROL_SAMPLING_FEQ:
            switch (setup->bRequest) {
                case AUDIO_REQUEST_SET_CUR:
                    memcpy((uint8_t *)&sampling_freq, *data, *len);
                    USB_LOG_DBG("Set ep:0x%02x %d Hz\r\n", ep, (int)sampling_freq);
                    usbd_audio_set_sampling_freq(busid, ep, sampling_freq);
                    break;
                case AUDIO_REQUEST_GET_CUR:
                case AUDIO_REQUEST_GET_MIN:
                case AUDIO_REQUEST_GET_MAX:
                case AUDIO_REQUEST_GET_RES:
                    sampling_freq = usbd_audio_get_sampling_freq(busid, ep);
                    memcpy(*data, &sampling_freq, 3);
                    USB_LOG_DBG("Get ep:0x%02x %d Hz\r\n", ep, (int)sampling_freq);
                    *len = 3;
                    break;
            }

            break;
        default:
            USB_LOG_WRN("Unhandled Audio Class control selector 0x%02x\r\n", control_selector);
            return -1;
    }
    return 0;
}

static int audio_class_interface_request_handler(uint8_t busid, struct usb_setup_packet *setup, uint8_t **data, uint32_t *len)
{
    USB_LOG_DBG("Audio Class request: "
                "bRequest 0x%02x\r\n",
                setup->bRequest);

    uint8_t entity_id;
    uint8_t ep;
    uint8_t subtype = 0x01;
    uint8_t control_selector;
    uint8_t ch;
    uint8_t mute;
    uint16_t volume;
    int volume_db = 0;
    uint32_t sampling_freq = 0;

    const char *mute_string[2] = { "off", "on" };

    entity_id = HI_BYTE(setup->wIndex);
    control_selector = HI_BYTE(setup->wValue);
    ch = LO_BYTE(setup->wValue);

    ARG_UNUSED(mute_string);

    for (uint8_t i = 0; i < g_usbd_audio[busid].num; i++) {
        if (g_usbd_audio[busid].table[i].bEntityId == entity_id) {
            subtype = g_usbd_audio[busid].table[i].bDescriptorSubtype;
            ep = g_usbd_audio[busid].table[i].ep;
            break;
        }
    }

    if (subtype == 0x01) {
        USB_LOG_ERR("Do not find subtype for 0x%02x\r\n", entity_id);
        return -1;
    }

    USB_LOG_DBG("Audio entity_id:%02x, subtype:%02x, cs:%02x\r\n", entity_id, subtype, control_selector);

    switch (subtype) {
        case AUDIO_CONTROL_FEATURE_UNIT:
            switch (control_selector) {
                case AUDIO_FU_CONTROL_MUTE:
                    if (g_usbd_audio[busid].uac_version < 0x0200) {
                        switch (setup->bRequest) {
                            case AUDIO_REQUEST_SET_CUR:
                                mute = (*data)[0];
                                usbd_audio_set_mute(busid, ep, ch, mute);
                                break;
                            case AUDIO_REQUEST_GET_CUR:
                                (*data)[0] = usbd_audio_get_mute(busid, ep, ch);
                                *len = 1;
                                break;
                            default:
                                USB_LOG_WRN("Unhandled Audio Class bRequest 0x%02x in cs 0x%02x\r\n", setup->bRequest, control_selector);
                                return -1;
                        }
                    } else {
                        switch (setup->bRequest) {
                            case AUDIO_REQUEST_CUR:
                                if (setup->bmRequestType & USB_REQUEST_DIR_MASK) {
                                    (*data)[0] = usbd_audio_get_mute(busid, ep, ch);
                                    *len = 1;
                                } else {
                                    mute = (*data)[0];
                                    usbd_audio_set_mute(busid, ep, ch, mute);
                                }
                                break;
                            default:
                                //USB_LOG_WRN("Unhandled Audio Class bRequest 0x%02x in cs 0x%02x\r\n", setup->bRequest, control_selector);
                                return -1;
                        }
                    }
                    break;
                case AUDIO_FU_CONTROL_VOLUME:
                    if (g_usbd_audio[busid].uac_version < 0x0200) {
                        switch (setup->bRequest) {
                            case AUDIO_REQUEST_SET_CUR:
                                memcpy(&volume, *data, *len);
                                if (volume < 0x8000) {
                                    volume_db = volume / 256;
                                } else if (volume > 0x8000) {
                                    volume_db = (0xffff - volume + 1) / -256;
                                }
                                volume_db += 128; /* 0 ~ 255 */
                                USB_LOG_DBG("Set ep:0x%02x ch:%d volume:0x%04x\r\n", ep, ch, volume);
                                usbd_audio_set_volume(busid, ep, ch, volume_db);
                                break;
                            case AUDIO_REQUEST_GET_CUR:
                                volume_db = usbd_audio_get_volume(busid, ep, ch);
                                volume_db -= 128;
                                if (volume_db >= 0) {
                                    volume = volume_db * 256;
                                } else {
                                    volume = volume_db * 256 + 0xffff + 1;
                                }
                                memcpy(*data, &volume, 2);
                                *len = 2;
                                break;
                            case AUDIO_REQUEST_GET_MIN:
                                (*data)[0] = 0x00; /* -2560/256 dB */
                                (*data)[1] = 0xdb;
                                *len = 2;
                                break;
                            case AUDIO_REQUEST_GET_MAX:
                                (*data)[0] = 0x00; /* 0 dB */
                                (*data)[1] = 0x00;
                                *len = 2;
                                break;
                            case AUDIO_REQUEST_GET_RES:
                                (*data)[0] = 0x00; /* -256/256 dB */
                                (*data)[1] = 0x01;
                                *len = 2;
                                break;
                            default:
                                USB_LOG_WRN("Unhandled Audio Class bRequest 0x%02x in cs 0x%02x\r\n", setup->bRequest, control_selector);
                                return -1;
                        }
                    } else {
                        switch (setup->bRequest) {
                            case AUDIO_REQUEST_CUR:
                                if (setup->bmRequestType & USB_REQUEST_DIR_MASK) {
                                    volume_db = usbd_audio_get_volume(busid, ep, ch);
                                    volume = volume_db;
                                    memcpy(*data, &volume, 2);
                                    *len = 2;
                                } else {
                                    memcpy(&volume, *data, *len);
                                    volume_db = volume;
                                    USB_LOG_DBG("Set ep:0x%02x ch:%d volume:0x%02x\r\n", ep, ch, volume);
                                    usbd_audio_set_volume(busid, ep, ch, volume_db);
                                }
                                break;
                            case AUDIO_REQUEST_RANGE:
                                if (setup->bmRequestType & USB_REQUEST_DIR_MASK) {
                                    *((uint16_t *)(*data + 0)) = 1;
                                    *((uint16_t *)(*data + 2)) = 0;
                                    *((uint16_t *)(*data + 4)) = 100;
                                    *((uint16_t *)(*data + 6)) = 1;
                                    *len = 8;
                                } else {
                                }
                                break;
                            default:
                                //USB_LOG_WRN("Unhandled Audio Class bRequest 0x%02x in cs 0x%02x\r\n", setup->bRequest, control_selector);
                                return -1;
                        }
                    }
                    break;

                default:
                    USB_LOG_WRN("Unhandled Audio Class cs 0x%02x \r\n", control_selector);
                    return -1;
            }
            break;
        case AUDIO_CONTROL_CLOCK_SOURCE:
            switch (control_selector) {
                case AUDIO_CS_CONTROL_SAM_FREQ:
                    switch (setup->bRequest) {
                        case AUDIO_REQUEST_CUR:
                            if (setup->bmRequestType & USB_REQUEST_DIR_MASK) {
                                sampling_freq = usbd_audio_get_sampling_freq(busid, ep);
                                memcpy(*data, &sampling_freq, 4);
                                USB_LOG_DBG("Get ep:0x%02x %d Hz\r\n", ep, (int)sampling_freq);
                                *len = 4;
                            } else {
                                memcpy(&sampling_freq, *data, setup->wLength);
                                USB_LOG_DBG("Set ep:0x%02x %d Hz\r\n", ep, (int)sampling_freq);
                                usbd_audio_set_sampling_freq(busid, ep, sampling_freq);
                            }
                            break;
                        case AUDIO_REQUEST_RANGE:
                            if (setup->bmRequestType & USB_REQUEST_DIR_MASK) {
                                uint8_t *sampling_freq_table = NULL;
                                uint16_t num;

                                usbd_audio_get_sampling_freq_table(busid, ep, &sampling_freq_table);
                                num = (uint16_t)((uint16_t)(sampling_freq_table[1] << 8) | ((uint16_t)sampling_freq_table[0]));
                                memcpy(*data, sampling_freq_table, (12 * num + 2));
                                *len = (12 * num + 2);
                            } else {
                            }
                            break;
                        default:
                            //USB_LOG_WRN("Unhandled Audio Class bRequest 0x%02x in cs 0x%02x\r\n", setup->bRequest, control_selector);
                            return -1;
                    }
                    break;
                case AUDIO_CS_CONTROL_CLOCK_VALID:
                    if (setup->bmRequestType & USB_REQUEST_DIR_MASK) {
                        (*data)[0] = 1;
                        *len = 1;
                    } else {
                        return -1;
                    }
                    break;

                default:
                    //USB_LOG_WRN("Unhandled Audio Class cs 0x%02x \r\n", control_selector);
                    return -1;
            }
            break;

        default:
            break;
    }
    return 0;
}

static void audio_notify_handler(uint8_t busid, uint8_t event, void *arg)
{
    switch (event) {
        case USBD_EVENT_RESET:

            break;

        case USBD_EVENT_SET_INTERFACE: {
            struct usb_interface_descriptor *intf = (struct usb_interface_descriptor *)arg;
            if (intf->bAlternateSetting) {
                usbd_audio_open(busid, intf->bInterfaceNumber);
            } else {
                usbd_audio_close(busid, intf->bInterfaceNumber);
            }
        }

        break;

        default:
            break;
    }
}

struct usbd_interface *usbd_audio_init_intf(uint8_t busid, 
                                            struct usbd_interface *intf,
                                            uint16_t uac_version,
                                            struct audio_entity_info *table,
                                            uint8_t num)
{
    if (uac_version < 0x0200) {
        intf->class_interface_handler = audio_class_interface_request_handler;
        intf->class_endpoint_handler = audio_class_endpoint_request_handler;
        intf->vendor_handler = NULL;
        intf->notify_handler = audio_notify_handler;
    } else {
        intf->class_interface_handler = audio_class_interface_request_handler;
        intf->class_endpoint_handler = NULL;
        intf->vendor_handler = NULL;
        intf->notify_handler = audio_notify_handler;
    }

    g_usbd_audio[busid].uac_version = uac_version;
    g_usbd_audio[busid].table = table;
    g_usbd_audio[busid].num = num;

    return intf;
}

__WEAK void usbd_audio_set_volume(uint8_t busid, uint8_t ep, uint8_t ch, int volume)
{
}

__WEAK int usbd_audio_get_volume(uint8_t busid, uint8_t ep, uint8_t ch)
{
    return 0;
}

__WEAK void usbd_audio_set_mute(uint8_t busid, uint8_t ep, uint8_t ch, bool mute)
{
}

__WEAK bool usbd_audio_get_mute(uint8_t busid, uint8_t ep, uint8_t ch)
{
    return 0;
}

__WEAK void usbd_audio_set_sampling_freq(uint8_t busid, uint8_t ep, uint32_t sampling_freq)
{
}

__WEAK uint32_t usbd_audio_get_sampling_freq(uint8_t busid, uint8_t ep)
{
    return 0;
}

__WEAK void usbd_audio_get_sampling_freq_table(uint8_t busid, uint8_t ep, uint8_t **sampling_freq_table)
{
}