/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed 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.
 */

#define LOG_TAG "audio_hw_usb"

#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
#include <log/log.h>
#include <cutils/str_parms.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <system/audio.h>
#include <tinyalsa/asoundlib.h>
#include <audio_hw.h>
#include <cutils/properties.h>
#include <ctype.h>
#include <math.h>

#ifdef USB_TUNNEL_ENABLED
#define USB_BUFF_SIZE           2048
#define CHANNEL_NUMBER_STR      "Channels: "
#define PLAYBACK_PROFILE_STR    "Playback:"
#define CAPTURE_PROFILE_STR     "Capture:"
#define DATA_PACKET_INTERVAL_STR "Data packet interval: "
#define USB_SIDETONE_GAIN_STR   "usb_sidetone_gain"
#define ABS_SUB(A, B) (((A) > (B)) ? ((A) - (B)):((B) - (A)))
#define SAMPLE_RATE_8000          8000
#define SAMPLE_RATE_11025         11025
#define DEFAULT_SERVICE_INTERVAL_US    1000

/* TODO: dynamically populate supported sample rates */
static uint32_t supported_sample_rates[] =
    {192000, 176400, 96000, 88200, 64000, 48000, 44100};
static uint32_t supported_sample_rates_mask[2];
static const uint32_t MAX_SAMPLE_RATE_SIZE =
        (sizeof(supported_sample_rates)/sizeof(supported_sample_rates[0]));

// assert on sizeof bm v/s size of rates if needed

typedef enum usb_usecase_type{
    USB_PLAYBACK = 0,
    USB_CAPTURE,
} usb_usecase_type_t;

enum {
    USB_SIDETONE_ENABLE_INDEX = 0,
    USB_SIDETONE_VOLUME_INDEX,
    USB_SIDETONE_MAX_INDEX,
};

struct usb_device_config {
    struct listnode list;
    unsigned int bit_width;
    unsigned int channel_count;
    unsigned int rate_size;
    unsigned int rates[MAX_SAMPLE_RATE_SIZE];
    unsigned long service_interval_us;
    usb_usecase_type_t type;
};

struct usb_card_config {
    struct listnode list;
    audio_devices_t usb_device_type;
    int usb_card;
    struct listnode usb_device_conf_list;
    struct mixer *usb_snd_mixer;
    int usb_sidetone_index[USB_SIDETONE_MAX_INDEX];
    int usb_sidetone_vol_min;
    int usb_sidetone_vol_max;
};

struct usb_module {
    struct listnode usb_card_conf_list;
    struct audio_device *adev;
    int sidetone_gain;
    bool is_capture_supported;
};

static struct usb_module *usbmod = NULL;
static bool usb_audio_debug_enable = false;
static int usb_sidetone_gain = 0;

static const char * const usb_sidetone_enable_str[] = {
    "Sidetone Playback Switch",
    "Mic Playback Switch",
};

static const char * const usb_sidetone_volume_str[] = {
    "Sidetone Playback Volume",
    "Mic Playback Volume",
};

static void usb_mixer_print_enum(struct mixer_ctl *ctl)
{
    unsigned int num_enums;
    unsigned int i;
    const char *string;

    num_enums = mixer_ctl_get_num_enums(ctl);

    for (i = 0; i < num_enums; i++) {
        string = mixer_ctl_get_enum_string(ctl, i);
        ALOGI("\t%s%s", mixer_ctl_get_value(ctl, 0) == (int)i ? ">" : "", string);
    }
}

static void usb_soundcard_detail_control(struct mixer *mixer, const char *control)
{
    struct mixer_ctl *ctl;
    enum mixer_ctl_type type;
    unsigned int num_values;
    unsigned int i;
    int min, max;

    if (isdigit(control[0]))
        ctl = mixer_get_ctl(mixer, atoi(control));
    else
        ctl = mixer_get_ctl_by_name(mixer, control);

    if (!ctl) {
        fprintf(stderr, "Invalid mixer control\n");
        return;
    }

    type = mixer_ctl_get_type(ctl);
    num_values = mixer_ctl_get_num_values(ctl);

    ALOGV("%s:", mixer_ctl_get_name(ctl));

    for (i = 0; i < num_values; i++) {
        switch (type) {
            case MIXER_CTL_TYPE_INT:
                ALOGV(" %d", mixer_ctl_get_value(ctl, i));
                break;
            case MIXER_CTL_TYPE_BOOL:
                ALOGV(" %s", mixer_ctl_get_value(ctl, i) ? "On" : "Off");
                break;
            case MIXER_CTL_TYPE_ENUM:
                usb_mixer_print_enum(ctl);
                break;
            case MIXER_CTL_TYPE_BYTE:
                ALOGV(" 0x%02x", mixer_ctl_get_value(ctl, i));
                break;
            default:
                ALOGV(" unknown");
                break;
        }
    }

    if (type == MIXER_CTL_TYPE_INT) {
        min = mixer_ctl_get_range_min(ctl);
        max = mixer_ctl_get_range_max(ctl);
        ALOGV(" (range %d->%d)", min, max);
    }
}

static void usb_soundcard_list_controls(struct mixer *mixer)
{
    struct mixer_ctl *ctl;
    const char *name, *type;
    unsigned int num_ctls, num_values;
    unsigned int i;

    num_ctls = mixer_get_num_ctls(mixer);

    ALOGV("Number of controls: %d\n", num_ctls);

    ALOGV("ctl\ttype\tnum\t%-40s value\n", "name");
    for (i = 0; i < num_ctls; i++) {
        ctl = mixer_get_ctl(mixer, i);
        if (ctl != NULL) {
            name = mixer_ctl_get_name(ctl);
            type = mixer_ctl_get_type_string(ctl);
            num_values = mixer_ctl_get_num_values(ctl);
            ALOGV("%d\t%s\t%d\t%-40s", i, type, num_values, name);
            if (name != NULL)
                usb_soundcard_detail_control(mixer, name);
        }
    }
}

static int usb_set_dev_id_mixer_ctl(unsigned int usb_usecase_type, int card,
                                    char *dev_mixer_ctl_name)
{
    struct mixer_ctl *ctl;
    unsigned int dev_token;
    const unsigned int pcm_device_number = 0;

    /*
     * usb_dev_token_id is 32 bit number and is defined as below:
     * usb_sound_card_idx(31:16) | usb PCM device ID(15:8) | usb_usecase_type(7:0)
     */
    dev_token = (card << 16 ) |
                (pcm_device_number << 8) | (usb_usecase_type & 0xFF);

    ctl = mixer_get_ctl_by_name(usbmod->adev->mixer, dev_mixer_ctl_name);
    if (!ctl) {
       ALOGE("%s: Could not get ctl for mixer cmd - %s",
             __func__, dev_mixer_ctl_name);
       return -EINVAL;
    }
    mixer_ctl_set_value(ctl, 0, dev_token);

    return 0;
}

static int usb_get_sample_rates(int type, char *rates_str,
                                struct usb_device_config *config)
{
    uint32_t i;
    char *next_sr_string, *temp_ptr;
    uint32_t sr, min_sr, max_sr, sr_size = 0;

    /* Sample rate string can be in any of the folloing two bit_widthes:
     * Rates: 8000 - 48000 (continuous)
     * Rates: 8000, 44100, 48000
     * Support both the bit_widths
     */
    ALOGV("%s: rates_str %s", __func__, rates_str);
    next_sr_string = strtok_r(rates_str, "Rates: ", &temp_ptr);
    if (next_sr_string == NULL) {
        ALOGE("%s: could not find min rates string", __func__);
        return -EINVAL;
    }
    if (strstr(rates_str, "continuous") != NULL) {
        min_sr = (uint32_t)atoi(next_sr_string);
        next_sr_string = strtok_r(NULL, " ,.-", &temp_ptr);
        if (next_sr_string == NULL) {
            ALOGE("%s: could not find max rates string", __func__);
            return -EINVAL;
        }
        max_sr = (uint32_t)atoi(next_sr_string);

        for (i = 0; i < MAX_SAMPLE_RATE_SIZE; i++) {
            if (supported_sample_rates[i] >= min_sr &&
                supported_sample_rates[i] <= max_sr) {
                config->rates[sr_size++] = supported_sample_rates[i];
                supported_sample_rates_mask[type] |= (1<<i);
                ALOGI_IF(usb_audio_debug_enable,
                    "%s: continuous sample rate supported_sample_rates[%d] %d",
                    __func__, i, supported_sample_rates[i]);
            }
        }
    } else {
        do {
            sr = (uint32_t)atoi(next_sr_string);
            for (i = 0; i < MAX_SAMPLE_RATE_SIZE; i++) {
                if (supported_sample_rates[i] == sr) {
                    ALOGI_IF(usb_audio_debug_enable,
                        "%s: sr %d, supported_sample_rates[%d] %d -> matches!!",
                        __func__, sr, i, supported_sample_rates[i]);
                    config->rates[sr_size++] = supported_sample_rates[i];
                    supported_sample_rates_mask[type] |= (1<<i);
                }
            }
            next_sr_string = strtok_r(NULL, " ,.-", &temp_ptr);
        } while (next_sr_string != NULL);
    }
    config->rate_size = sr_size;
    return 0;
}

static int usb_get_service_interval(const char *interval_str_start,
                                    struct usb_device_config *usb_device_info)
{
    unsigned long interval = 0;
    char time_unit[8] = {0};
    int multiplier = 0;
    char *eol = strchr(interval_str_start, '\n');
    if (!eol) {
        ALOGE("%s: No EOL found", __func__);
        return -1;
    }
    char *tmp = (char *)calloc(1, eol-interval_str_start+1);
    if (!tmp) {
        ALOGE("%s: failed to allocate tmp", __func__);
        return -1;
    }
    memcpy(tmp, interval_str_start, eol-interval_str_start);
    sscanf(tmp, "%lu %2s", &interval, &time_unit[0]);
    if (!strcmp(time_unit, "us")) {
        multiplier = 1;
    } else if (!strcmp(time_unit, "ms")) {
        multiplier = 1000;
    } else if (!strcmp(time_unit, "s")) {
        multiplier = 1000000;
    } else {
        ALOGE("%s: unknown time_unit %s, assume default", __func__, time_unit);
        interval = DEFAULT_SERVICE_INTERVAL_US;
        multiplier = 1;
    }
    interval *= multiplier;
    ALOGV("%s: set service_interval_us %lu", __func__, interval);
    usb_device_info->service_interval_us = interval;
    free(tmp);
    return 0;
}


static int usb_get_capability(int type,
                              struct usb_card_config *usb_card_info,
                              int card)
{
    int32_t size = 0;
    int32_t fd=-1;
    int32_t channels_no;
    char *str_start = NULL;
    char *str_end = NULL;
    char *channel_start = NULL;
    char *bit_width_start = NULL;
    char *rates_str_start = NULL;
    char *target = NULL;
    char *read_buf = NULL;
    char *rates_str = NULL;
    char *interval_str_start = NULL;
    char path[128];
    int ret = 0;
    char *bit_width_str = NULL;
    struct usb_device_config * usb_device_info;
    bool check = false;
    int tries=5;

    memset(path, 0, sizeof(path));
    ALOGV("%s: for %s", __func__, (type == USB_PLAYBACK) ?
          PLAYBACK_PROFILE_STR : CAPTURE_PROFILE_STR);

    /* TODO: convert the below to using alsa_utils */
    ret = snprintf(path, sizeof(path), "/proc/asound/card%u/stream0",
             card);
    if (ret < 0) {
        ALOGE("%s: failed on snprintf (%d) to path %s\n",
          __func__, ret, path);
        goto done;
    }

    // TODO: figure up if this wait is needed any more
    while (tries--) {
        if (access(path, F_OK) < 0) {
            ALOGW("stream %s doesn't exist retrying\n", path);
            sleep(1);
            continue;
        }
    }

    fd = open(path, O_RDONLY);
    if (fd <0) {
        ALOGE("%s: error failed to open config file %s error: %d\n",
              __func__, path, errno);
        ret = -EINVAL;
        goto done;
    }

    read_buf = (char *)calloc(1, USB_BUFF_SIZE + 1);

    if (!read_buf) {
        ALOGE("Failed to create read_buf");
        ret = -ENOMEM;
        goto done;
    }

    if(read(fd, read_buf, USB_BUFF_SIZE) < 0) {
        ALOGE("file read error\n");
        goto done;
    }
    str_start = strstr(read_buf, ((type == USB_PLAYBACK) ?
                       PLAYBACK_PROFILE_STR : CAPTURE_PROFILE_STR));
    if (str_start == NULL) {
        ALOGE("%s: error %s section not found in usb config file",
               __func__, ((type == USB_PLAYBACK) ?
               PLAYBACK_PROFILE_STR : CAPTURE_PROFILE_STR));
        ret = -EINVAL;
        goto done;
    }
    str_end = strstr(read_buf, ((type == USB_PLAYBACK) ?
                       CAPTURE_PROFILE_STR : PLAYBACK_PROFILE_STR));
    if (str_end > str_start)
        check = true;

    ALOGV("%s: usb_config = %s, check %d\n", __func__, str_start, check);

    while (str_start != NULL) {
        str_start = strstr(str_start, "Altset");
        if ((str_start == NULL) || (check  && (str_start >= str_end))) {
            ALOGV("%s: done parsing %s\n", __func__, str_start);
            break;
        }
        ALOGV("%s: remaining string %s\n", __func__, str_start);
        str_start += sizeof("Altset");
        usb_device_info = calloc(1, sizeof(struct usb_device_config));
        if (usb_device_info == NULL) {
            ALOGE("%s: error unable to allocate memory",
                  __func__);
            ret = -ENOMEM;
            break;
        }
        usb_device_info->type = type;
        /* Bit bit_width parsing */
        bit_width_start = strstr(str_start, "Format: ");
        if (bit_width_start == NULL) {
            ALOGI("%s: Could not find bit_width string", __func__);
            free(usb_device_info);
            continue;
        }
        target = strchr(bit_width_start, '\n');
        if (target == NULL) {
            ALOGI("%s:end of line not found", __func__);
            free(usb_device_info);
            continue;
        }
        size = target - bit_width_start;
        if ((bit_width_str = (char *)malloc(size + 1)) == NULL) {
            ALOGE("%s: unable to allocate memory to hold bit width strings",
                  __func__);
            ret = -EINVAL;
            free(usb_device_info);
            break;
        }
        memcpy(bit_width_str, bit_width_start, size);
        bit_width_str[size] = '\0';
        if (strstr(bit_width_str, "S16_LE"))
            usb_device_info->bit_width = 16;
        else if (strstr(bit_width_str, "S24_LE"))
            usb_device_info->bit_width = 24;
        else if (strstr(bit_width_str, "S24_3LE"))
            usb_device_info->bit_width = 24;
        else if (strstr(bit_width_str, "S32_LE"))
            usb_device_info->bit_width = 32;

        if (bit_width_str)
            free(bit_width_str);

        /* channels parsing */
        channel_start = strstr(str_start, CHANNEL_NUMBER_STR);
        if (channel_start == NULL) {
            ALOGI("%s: could not find Channels string", __func__);
            free(usb_device_info);
            continue;
        }
        channels_no = atoi(channel_start + strlen(CHANNEL_NUMBER_STR));
        usb_device_info->channel_count =  channels_no;

        /* Sample rates parsing */
        rates_str_start = strstr(str_start, "Rates: ");
        if (rates_str_start == NULL) {
            ALOGI("%s: cant find rates string", __func__);
            free(usb_device_info);
            continue;
        }
        target = strchr(rates_str_start, '\n');
        if (target == NULL) {
            ALOGI("%s: end of line not found", __func__);
            free(usb_device_info);
            continue;
        }
        size = target - rates_str_start;
        if ((rates_str = (char *)malloc(size + 1)) == NULL) {
            ALOGE("%s: unable to allocate memory to hold sample rate strings",
                  __func__);
            ret = -EINVAL;
            free(usb_device_info);
            break;
        }
        memcpy(rates_str, rates_str_start, size);
        rates_str[size] = '\0';
        ret = usb_get_sample_rates(type, rates_str, usb_device_info);
        if (rates_str)
            free(rates_str);
        if (ret < 0) {
            ALOGE("%s: error unable to get sample rate values",
                  __func__);
            free(usb_device_info);
            continue;
        }
        // Data packet interval is an optional field.
        // Assume 1ms interval if this cannot be read
        usb_device_info->service_interval_us = DEFAULT_SERVICE_INTERVAL_US;
        interval_str_start = strstr(str_start, DATA_PACKET_INTERVAL_STR);
        if (interval_str_start != NULL) {
            interval_str_start += strlen(DATA_PACKET_INTERVAL_STR);
            ret = usb_get_service_interval(interval_str_start, usb_device_info);
            if (ret < 0) {
                ALOGE("%s: error unable to get service interval, assume default",
                      __func__);
            }
        }
        /* Add to list if every field is valid */
        list_add_tail(&usb_card_info->usb_device_conf_list,
                      &usb_device_info->list);
    }

done:
    if (fd >= 0) close(fd);
    if (read_buf) free(read_buf);
    return ret;
}

static int usb_get_device_playback_config(struct usb_card_config *usb_card_info,
                                    int card)
{
    int ret;

    /* get capabilities */
    if ((ret = usb_get_capability(USB_PLAYBACK, usb_card_info, card))) {
        ALOGE("%s: could not get Playback capabilities from usb device",
               __func__);
        goto exit;
    }
    usb_set_dev_id_mixer_ctl(USB_PLAYBACK, card, "USB_AUDIO_RX dev_token");

exit:

    return ret;
}

static int usb_get_device_capture_config(struct usb_card_config *usb_card_info,
                                      int card)
{
    int ret;

    /* get capabilities */
    if ((ret = usb_get_capability(USB_CAPTURE, usb_card_info, card))) {
        ALOGE("%s: could not get Playback capabilities from usb device",
               __func__);
        goto exit;
    }
    usb_set_dev_id_mixer_ctl(USB_CAPTURE, card, "USB_AUDIO_TX dev_token");

exit:
    return ret;
}

static void usb_get_sidetone_mixer(struct usb_card_config *usb_card_info)
{
    struct mixer_ctl *ctl;
    unsigned int index;

    for (index = 0; index < USB_SIDETONE_MAX_INDEX; index++)
        usb_card_info->usb_sidetone_index[index] = -1;

    usb_card_info->usb_snd_mixer = mixer_open(usb_card_info->usb_card);
    for (index = 0;
         index < sizeof(usb_sidetone_enable_str)/sizeof(usb_sidetone_enable_str[0]);
         index++) {
        ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,
                                    usb_sidetone_enable_str[index]);
        if (ctl) {
            usb_card_info->usb_sidetone_index[USB_SIDETONE_ENABLE_INDEX] = index;
            /* Disable device sidetone by default */
            mixer_ctl_set_value(ctl, 0, false);
            ALOGV("%s:: sidetone mixer Control found(%s) ... disabling by default",
                   __func__, usb_sidetone_enable_str[index]);
            break;
        }
    }
#ifdef USB_SIDETONE_VOLUME
    for (index = 0;
         index < sizeof(usb_sidetone_volume_str)/sizeof(usb_sidetone_volume_str[0]);
         index++) {
        ctl = mixer_get_ctl_by_name(usb_card_info->usb_snd_mixer,
                                    usb_sidetone_volume_str[index]);
        if (ctl) {
            usb_card_info->usb_sidetone_index[USB_SIDETONE_VOLUME_INDEX] = index;
            usb_card_info->usb_sidetone_vol_min = mixer_ctl_get_range_min(ctl);
            usb_card_info->usb_sidetone_vol_max = mixer_ctl_get_range_max(ctl);
            break;
        }
    }
#endif // USB_SIDETONE_VOLUME
    if ((usb_card_info->usb_snd_mixer != NULL) && (usb_audio_debug_enable))
        usb_soundcard_list_controls(usb_card_info->usb_snd_mixer);

    return;
}

static inline bool usb_output_device(audio_devices_t device) {
    // ignore accessory for now
    if (device == AUDIO_DEVICE_OUT_USB_ACCESSORY) {
        return false;
    }
    return audio_is_usb_out_device(device);
}

static inline bool usb_input_device(audio_devices_t device) {
    // ignore accessory for now
    if (device == AUDIO_DEVICE_IN_USB_ACCESSORY) {
        return false;
    }
    return audio_is_usb_in_device(device);
}

static bool usb_valid_device(audio_devices_t device)
{
    return usb_output_device(device) ||
           usb_input_device(device);
}

static void usb_print_active_device(void){
    struct listnode *node_i, *node_j;
    struct usb_device_config *dev_info;
    struct usb_card_config *card_info;
    unsigned int i;

    ALOGI("%s", __func__);
    list_for_each(node_i, &usbmod->usb_card_conf_list) {
        card_info = node_to_item(node_i, struct usb_card_config, list);
        ALOGI("%s: card_dev_type (0x%x), card_no(%d)",
               __func__,  card_info->usb_device_type, card_info->usb_card);
        list_for_each(node_j, &card_info->usb_device_conf_list) {
            dev_info = node_to_item(node_j, struct usb_device_config, list);
            ALOGI("%s: bit-width(%d) channel(%d)",
                   __func__, dev_info->bit_width, dev_info->channel_count);
            for (i =  0; i < dev_info->rate_size; i++)
                ALOGI("%s: rate %d", __func__, dev_info->rates[i]);
        }
    }
}

static bool usb_get_best_bit_width(
                            struct listnode *dev_list,
                            unsigned int stream_bit_width,
                            unsigned int *bit_width)
{
    struct listnode *node_i;
    struct usb_device_config *dev_info;
    unsigned int candidate = 0;

    list_for_each(node_i, dev_list) {
        dev_info = node_to_item(node_i, struct usb_device_config, list);
        ALOGI_IF(usb_audio_debug_enable,
                 "%s: USB bw(%d), stream bw(%d), candidate(%d)",
                 __func__, dev_info->bit_width,
                 stream_bit_width, candidate);
        if (candidate == 0) {
            ALOGV("%s: candidate bit-width (%d)",
                  __func__, dev_info->bit_width);
            candidate = dev_info->bit_width;
        } else if (dev_info->bit_width > candidate) {
            candidate = dev_info->bit_width;
            ALOGV("%s: Found better candidate bit-width (%d)",
                  __func__, dev_info->bit_width);
        }
    }
    ALOGV("%s: Use the best candidate bw(%d)",
          __func__, candidate);
    *bit_width = candidate;
exit:
    return true;
}

static bool usb_get_best_match_for_channels(
                            struct listnode *dev_list,
                            unsigned int bit_width,
                            unsigned int stream_ch,
                            unsigned int *channel_count)
{
    struct listnode *node_i;
    struct usb_device_config *dev_info;
    unsigned int candidate = 0;

    list_for_each(node_i, dev_list) {
        dev_info = node_to_item(node_i, struct usb_device_config, list);
        ALOGI_IF(usb_audio_debug_enable,
                 "%s: USB ch(%d)bw(%d), stream ch(%d)bw(%d), candidate(%d)",
                 __func__, dev_info->channel_count, dev_info->bit_width,
                 stream_ch, bit_width, candidate);
        if (dev_info->bit_width != bit_width)
            continue;
        if (dev_info->channel_count== stream_ch) {
            *channel_count = dev_info->channel_count;
            ALOGV("%s: Found match channels (%d)",
                  __func__, dev_info->channel_count);
            goto exit;
        } else if (candidate == 0)
                candidate = dev_info->channel_count;
            /*
            * If stream channel is 4, USB supports both 3 and 5, then
            *  higher channel 5 is picked up instead of 3
            */
        else if (ABS_SUB(stream_ch, dev_info->channel_count) <
                 ABS_SUB(stream_ch, candidate)) {
            candidate = dev_info->channel_count;
        } else if ((ABS_SUB(stream_ch, dev_info->channel_count) ==
                    ABS_SUB(stream_ch, candidate)) &&
                   (dev_info->channel_count > candidate)) {
            candidate = dev_info->channel_count;
        }
    }
    ALOGV("%s: No match found, use the best candidate ch(%d)",
          __func__, candidate);
    *channel_count = candidate;
exit:
    return true;

}

static bool usb_sample_rate_multiple(
                                     unsigned int stream_sample_rate,
                                     unsigned int base)
{
    return (((stream_sample_rate / base) * base) == stream_sample_rate);
}

static bool usb_find_sample_rate_candidate(unsigned int base,
                                           unsigned stream_rate,
                                           unsigned int usb_rate,
                                           unsigned int cur_candidate,
                                           unsigned int *update_candidate)
{
    /* For sample rate, we should consider  fracational sample rate as high priority.
    * For example, if the stream is 88.2kHz and USB device support both 44.1kH and
    * 48kHz sample rate, we should pick 44.1kHz instead of 48kHz
    */
    if (!usb_sample_rate_multiple(cur_candidate, base) &&
       usb_sample_rate_multiple(usb_rate, base)) {
        *update_candidate = usb_rate;
    } else if (usb_sample_rate_multiple(cur_candidate, base) &&
               usb_sample_rate_multiple(usb_rate, base)) {
        if (ABS_SUB(stream_rate, usb_rate) <
            ABS_SUB(stream_rate, cur_candidate)) {
            *update_candidate = usb_rate;
        } else if ((ABS_SUB(stream_rate, usb_rate) ==
                    ABS_SUB(stream_rate, cur_candidate)) &&
                   (usb_rate > cur_candidate)) {
            *update_candidate = usb_rate;
        }
    } else if (!usb_sample_rate_multiple(cur_candidate, base) &&
               !usb_sample_rate_multiple(usb_rate, base)) {
        if (ABS_SUB(stream_rate, usb_rate) <
            ABS_SUB(stream_rate, cur_candidate)) {
            *update_candidate = usb_rate;
        } else if ((ABS_SUB(stream_rate, usb_rate) ==
                    ABS_SUB(stream_rate, cur_candidate)) &&
                   (usb_rate > cur_candidate)) {
            *update_candidate = usb_rate;
        }
    }
    return true;
}

static bool usb_get_best_match_for_sample_rate (
                            struct listnode *dev_list,
                            unsigned int bit_width,
                            unsigned int channel_count,
                            unsigned int stream_sample_rate,
                            unsigned int *sr,
                            unsigned int service_interval,
                            bool do_service_interval_check)
{
    struct listnode *node_i;
    struct usb_device_config *dev_info;
    unsigned int candidate = 48000;
    unsigned int base = SAMPLE_RATE_8000;
    bool multiple_8k = usb_sample_rate_multiple(stream_sample_rate, base);
    unsigned int i;

    ALOGV("%s: stm ch(%d)bw(%d)sr(%d), stream sample multiple of 8kHz(%d)",
        __func__, channel_count, bit_width, stream_sample_rate, multiple_8k);

    list_for_each(node_i, dev_list) {
        dev_info = node_to_item(node_i, struct usb_device_config, list);
        ALOGI_IF(usb_audio_debug_enable,
                 "%s: USB ch(%d)bw(%d), stm ch(%d)bw(%d)sr(%d), candidate(%d)",
                 __func__, dev_info->channel_count, dev_info->bit_width,
                 channel_count, bit_width, stream_sample_rate, candidate);
        if ((dev_info->bit_width != bit_width) ||
            (dev_info->channel_count != channel_count) ||
            (do_service_interval_check && (dev_info->service_interval_us !=
                                           service_interval)))
            continue;

        candidate = 0;
        for (i = 0; i < dev_info->rate_size; i++) {
            ALOGI_IF(usb_audio_debug_enable,
                     "%s: USB ch(%d)bw(%d)sr(%d), stm ch(%d)bw(%d)sr(%d), candidate(%d)",
                     __func__, dev_info->channel_count,
                     dev_info->bit_width, dev_info->rates[i],
                     channel_count, bit_width, stream_sample_rate, candidate);
            if (stream_sample_rate == dev_info->rates[i]) {
                *sr = dev_info->rates[i];
                ALOGV("%s: Found match sample rate (%d)",
                      __func__, dev_info->rates[i]);
                goto exit;
            } else if (candidate == 0) {
                    candidate = dev_info->rates[i];
                /*
                * For sample rate, we should consider  fracational sample rate as high priority.
                * For example, if the stream is 88.2kHz and USB device support both 44.1kH and
                * 48kHz sample rate, we should pick 44.1kHz instead of 48kHz
                */
            } else if (multiple_8k) {
                usb_find_sample_rate_candidate(SAMPLE_RATE_8000,
                                               stream_sample_rate,
                                               dev_info->rates[i],
                                               candidate,
                                               &candidate);
            } else {
                usb_find_sample_rate_candidate(SAMPLE_RATE_11025,
                                               stream_sample_rate,
                                               dev_info->rates[i],
                                               candidate,
                                               &candidate);
            }
        }
    }
    ALOGV("%s: No match found, use the best candidate sr(%d)",
          __func__, candidate);
    *sr = candidate;
exit:
    return true;
}

static bool usb_audio_backend_apply_policy(struct listnode *dev_list,
                                           unsigned int *bit_width,
                                           unsigned int *sample_rate,
                                           unsigned int *channel_count)
{
    ALOGV("%s: from stream: bit-width(%d) sample_rate(%d) channels (%d)",
           __func__, *bit_width, *sample_rate, *channel_count);
    if (list_empty(dev_list)) {
        *sample_rate = 48000;
        *bit_width = 16;
        *channel_count = 2;
        ALOGE("%s: list is empty,fall back to default setting", __func__);
        goto exit;
    }
    usb_get_best_bit_width(dev_list, *bit_width, bit_width);
    usb_get_best_match_for_channels(dev_list,
                                    *bit_width,
                                    *channel_count,
                                    channel_count);
    usb_get_best_match_for_sample_rate(dev_list,
                                       *bit_width,
                                       *channel_count,
                                       *sample_rate,
                                       sample_rate,
                                       0 /*service int*/,
                                       false /*do service int check*/);
exit:
    ALOGV("%s: Updated sample rate per profile: bit-width(%d) rate(%d) chs(%d)",
           __func__, *bit_width, *sample_rate, *channel_count);
    return true;
}

static int usb_get_sidetone_gain(struct usb_card_config *card_info)
{
    int gain = card_info->usb_sidetone_vol_min + usbmod->sidetone_gain;
    if (gain > card_info->usb_sidetone_vol_max)
        gain = card_info->usb_sidetone_vol_max;
    return gain;
}

void audio_extn_usb_set_sidetone_gain(struct str_parms *parms,
                                char *value, int len)
{
    int err;

    err = str_parms_get_str(parms, USB_SIDETONE_GAIN_STR,
                            value, len);
    if (err >= 0) {
        usb_sidetone_gain = pow(10.0, (float)(atoi(value))/10.0);
        ALOGV("%s: sidetone gain(%s) decimal %d",
              __func__, value, usb_sidetone_gain);
        str_parms_del(parms, USB_SIDETONE_GAIN_STR);
    }
    return;
}

int audio_extn_usb_enable_sidetone(int device, bool enable)
{
    int ret = -ENODEV;
    struct listnode *node_i;
    struct usb_card_config *card_info;
    int i;
    ALOGV("%s: card_dev_type (0x%x), sidetone enable(%d)",
           __func__,  device, enable);

    list_for_each(node_i, &usbmod->usb_card_conf_list) {
        card_info = node_to_item(node_i, struct usb_card_config, list);
        ALOGV("%s: card_dev_type (0x%x), card_no(%d)",
               __func__,  card_info->usb_device_type, card_info->usb_card);
        if (usb_output_device(card_info->usb_device_type)) {
            if ((i = card_info->usb_sidetone_index[USB_SIDETONE_ENABLE_INDEX]) != -1) {
                struct mixer_ctl *ctl = mixer_get_ctl_by_name(
                                card_info->usb_snd_mixer,
                                usb_sidetone_enable_str[i]);
                if (ctl)
                    mixer_ctl_set_value(ctl, 0, enable);
                else
                    break;

#ifdef USB_SIDETONE_VOLUME
                if ((i = card_info->usb_sidetone_index[USB_SIDETONE_VOLUME_INDEX]) != -1) {
                    ctl = mixer_get_ctl_by_name(
                                card_info->usb_snd_mixer,
                                usb_sidetone_volume_str[i]);
                    if (ctl == NULL)
                        ALOGV("%s: sidetone gain mixer command is not found",
                               __func__);
                    else if (enable)
                        mixer_ctl_set_value(ctl, 0,
                                            usb_get_sidetone_gain(card_info));
                }
#endif // USB_SIDETONE_VOLUME
                ret = 0;
                break;
            }
        }
    }
    return ret;
}

bool audio_extn_usb_is_config_supported(unsigned int *bit_width,
                                        unsigned int *sample_rate,
                                        unsigned int *channel_count,
                                        bool is_playback)
{
    struct listnode *node_i;
    struct usb_card_config *card_info;

    ALOGV("%s: from stream: bit-width(%d) sample_rate(%d) ch(%d) is_playback(%d)",
           __func__, *bit_width, *sample_rate, *channel_count, is_playback);
    list_for_each(node_i, &usbmod->usb_card_conf_list) {
        card_info = node_to_item(node_i, struct usb_card_config, list);
        ALOGI_IF(usb_audio_debug_enable,
                 "%s: card_dev_type (0x%x), card_no(%d)",
                 __func__,  card_info->usb_device_type, card_info->usb_card);
        /* Currently only apply the first playback sound card configuration */
        if ((is_playback && usb_output_device(card_info->usb_device_type)) ||
            (!is_playback && usb_input_device(card_info->usb_device_type))) {
            usb_audio_backend_apply_policy(&card_info->usb_device_conf_list,
                                           bit_width,
                                           sample_rate,
                                           channel_count);
            break;
        }
    }
    ALOGV("%s: updated: bit-width(%d) sample_rate(%d) channels (%d)",
           __func__, *bit_width, *sample_rate, *channel_count);

    return true;
}

#define _MAX(x, y) (((x) >= (y)) ? (x) : (y))
#define _MIN(x, y) (((x) <= (y)) ? (x) : (y))

int audio_extn_usb_get_max_channels(bool is_playback)
{
    struct listnode *node_i, *node_j;
    struct usb_device_config *dev_info;
    struct usb_card_config *card_info;
    unsigned int max_ch = 1;
    list_for_each(node_i, &usbmod->usb_card_conf_list) {
            card_info = node_to_item(node_i, struct usb_card_config, list);
            if (usb_output_device(card_info->usb_device_type) && !is_playback)
                continue;
            else if (usb_input_device(card_info->usb_device_type) && is_playback)
                continue;

            list_for_each(node_j, &card_info->usb_device_conf_list) {
                dev_info = node_to_item(node_j, struct usb_device_config, list);
                max_ch = _MAX(max_ch, dev_info->channel_count);
            }
    }

    return max_ch;
}

int audio_extn_usb_get_max_bit_width(bool is_playback)
{
    struct listnode *node_i, *node_j;
    struct usb_device_config *dev_info;
    struct usb_card_config *card_info;
    unsigned int max_bw = 16;
    list_for_each(node_i, &usbmod->usb_card_conf_list) {
            card_info = node_to_item(node_i, struct usb_card_config, list);
            if (usb_output_device(card_info->usb_device_type) && !is_playback)
                continue;
            else if (usb_input_device(card_info->usb_device_type) && is_playback)
                continue;

            list_for_each(node_j, &card_info->usb_device_conf_list) {
                dev_info = node_to_item(node_j, struct usb_device_config, list);
                max_bw = _MAX(max_bw, dev_info->bit_width);
            }
    }

    return max_bw;
}

int audio_extn_usb_sup_sample_rates(bool is_playback,
                                    uint32_t *sample_rates,
                                    uint32_t sample_rate_size)
{
    struct listnode *node_i, *node_j;
    struct usb_device_config *dev_info;
    struct usb_card_config *card_info;

    int type = is_playback ? USB_PLAYBACK : USB_CAPTURE;

    ALOGV("%s supported_sample_rates_mask 0x%x", __func__, supported_sample_rates_mask[type]);
    uint32_t bm = supported_sample_rates_mask[type];
    uint32_t tries = _MIN(sample_rate_size, (uint32_t)__builtin_popcount(bm));

    int i = 0;
    while (tries--) {
        int idx = __builtin_ffs(bm) - 1;
        sample_rates[i++] = supported_sample_rates[idx];
        bm &= ~(1<<idx);
    }

    return i;
}

bool audio_extn_usb_is_capture_supported()
{
    if (usbmod == NULL) {
        ALOGE("%s: USB device object is NULL", __func__);
        return false;
    }
    ALOGV("%s: capture_supported %d",__func__,usbmod->is_capture_supported);
    return usbmod->is_capture_supported;
}

void audio_extn_usb_add_device(audio_devices_t device, int card)
{
    struct usb_card_config *usb_card_info;
    char check_debug_enable[PROPERTY_VALUE_MAX];
    struct listnode *node_i;

    property_get("audio.usb.enable.debug", check_debug_enable, NULL);
    if (atoi(check_debug_enable)) {
        usb_audio_debug_enable = true;
    }

    ALOGI_IF(usb_audio_debug_enable,
             "%s: parameters device(0x%x), card(%d)",
             __func__, device, card);
    if (usbmod == NULL) {
        ALOGE("%s: USB device object is NULL", __func__);
        goto exit;
    }

    if (!(usb_valid_device(device)) || (card < 0)) {
        ALOGE("%s:device(0x%x), card(%d)",
              __func__, device, card);
        goto exit;
    }

    list_for_each(node_i, &usbmod->usb_card_conf_list) {
        usb_card_info = node_to_item(node_i, struct usb_card_config, list);
        ALOGI_IF(usb_audio_debug_enable,
                 "%s: list has capability for card_dev_type (0x%x), card_no(%d)",
                 __func__,  usb_card_info->usb_device_type, usb_card_info->usb_card);
        /* If we have cached the capability */
        if ((usb_card_info->usb_device_type == device) && (usb_card_info->usb_card == card)) {
            ALOGV("%s: capability for device(0x%x), card(%d) is cached, no need to update",
                  __func__, device, card);
            goto exit;
        }
    }
    usb_card_info = calloc(1, sizeof(struct usb_card_config));
    if (usb_card_info == NULL) {
        ALOGE("%s: error unable to allocate memory",
              __func__);
        goto exit;
    }
    list_init(&usb_card_info->usb_device_conf_list);
    if (usb_output_device(device)) {
        if (!usb_get_device_playback_config(usb_card_info, card)){
            usb_card_info->usb_card = card;
            usb_card_info->usb_device_type = device;
            usb_get_sidetone_mixer(usb_card_info);
            list_add_tail(&usbmod->usb_card_conf_list, &usb_card_info->list);
            goto exit;
        }
    } else if (usb_input_device(device)) {
        if (!usb_get_device_capture_config(usb_card_info, card)) {
            usb_card_info->usb_card = card;
            usb_card_info->usb_device_type = device;
            usbmod->is_capture_supported = true;
            list_add_tail(&usbmod->usb_card_conf_list, &usb_card_info->list);
            goto exit;
        }
    } else {
        ALOGW("%s: unknown device 0x%x", __func__, device);
    }
    /* free memory in error case */
    if (usb_card_info != NULL)
        free(usb_card_info);
exit:
    if (usb_audio_debug_enable)
        usb_print_active_device();
    return;
}

void audio_extn_usb_remove_device(audio_devices_t device, int card)
{
    struct listnode *node_i, *temp_i;
    struct listnode *node_j, *temp_j;
    struct usb_device_config *dev_info;
    struct usb_card_config *card_info;
    unsigned int i;

    ALOGV("%s: device(0x%x), card(%d)",
           __func__, device, card);

    if (usbmod == NULL) {
        ALOGE("%s: USB device object is NULL", __func__);
        goto exit;
    }

    if (!(usb_valid_device(device)) || (card < 0)) {
        ALOGE("%s: Invalid parameters device(0x%x), card(%d)",
              __func__, device, card);
        goto exit;
    }
    list_for_each_safe(node_i, temp_i, &usbmod->usb_card_conf_list) {
        card_info = node_to_item(node_i, struct usb_card_config, list);
        ALOGV("%s: card_dev_type (0x%x), card_no(%d)",
               __func__,  card_info->usb_device_type, card_info->usb_card);
        if ((device == card_info->usb_device_type) && (card == card_info->usb_card)){
            list_for_each_safe(node_j, temp_j, &card_info->usb_device_conf_list) {
                dev_info = node_to_item(node_j, struct usb_device_config, list);
                ALOGV("%s: bit-width(%d) channel(%d)",
                       __func__, dev_info->bit_width, dev_info->channel_count);
                for (i =  0; i < dev_info->rate_size; i++)
                    ALOGV("%s: rate %d", __func__, dev_info->rates[i]);

                list_remove(node_j);
                free(node_to_item(node_j, struct usb_device_config, list));
            }
            list_remove(node_i);
            if (card_info->usb_snd_mixer) {
                mixer_close(card_info->usb_snd_mixer);
            }
            free(node_to_item(node_i, struct usb_card_config, list));
        }
    }
    if (audio_is_usb_in_device(device)) { // XXX not sure if we need to check for card
        usbmod->is_capture_supported = false;
        supported_sample_rates_mask[USB_CAPTURE] = 0;
    } else {
        supported_sample_rates_mask[USB_PLAYBACK] = 0;
    }

exit:
    if (usb_audio_debug_enable)
        usb_print_active_device();

    return;
}

bool audio_extn_usb_alive(int card) {
    char path[PATH_MAX] = {0};
    // snprintf should never fail
    (void) snprintf(path, sizeof(path), "/proc/asound/card%u/stream0", card);
    return access(path, F_OK) == 0;
}

unsigned long audio_extn_usb_find_service_interval(bool min,
                                                   bool playback) {
    struct usb_card_config *card_info;
    struct usb_device_config *dev_info;
    struct listnode *node_i;
    struct listnode *node_j;
    unsigned long interval_us = min ? ULONG_MAX : 1; // 0 is invalid
    list_for_each(node_i, &usbmod->usb_card_conf_list) {
        card_info = node_to_item(node_i, struct usb_card_config, list);
        list_for_each(node_j, &card_info->usb_device_conf_list) {
            dev_info = node_to_item(node_j, struct usb_device_config, list);
            if ((playback && (dev_info->type == USB_PLAYBACK)) ||
                (!playback && (dev_info->type == USB_CAPTURE))) {
                interval_us = min ?
                        _MIN(interval_us, dev_info->service_interval_us) :
                        _MAX(interval_us, dev_info->service_interval_us);
            }
        }
        break;
    }
    return interval_us;
}

int audio_extn_usb_altset_for_service_interval(bool playback,
                                               unsigned long service_interval,
                                               uint32_t *bit_width,
                                               uint32_t *sample_rate,
                                               uint32_t *channel_count)
{
    struct usb_card_config *card_info;
    struct usb_device_config *dev_info;
    struct listnode *node_i;
    struct listnode *node_j;
    uint32_t bw = 0;
    uint32_t ch = 0;
    uint32_t sr = 0;
    list_for_each(node_i, &usbmod->usb_card_conf_list) {
        /* Currently only apply the first playback sound card configuration */
        card_info = node_to_item(node_i, struct usb_card_config, list);
        list_for_each(node_j, &card_info->usb_device_conf_list) {
            dev_info = node_to_item(node_j, struct usb_device_config, list);
            if ((playback && dev_info->type == USB_PLAYBACK) ||
                (!playback && dev_info->type == USB_CAPTURE)) {
                if (dev_info->service_interval_us != service_interval)
                    continue;
                if (dev_info->bit_width > bw) {
                    bw = dev_info->bit_width;
                    ch = dev_info->channel_count;
                } else if (dev_info->bit_width == bw &&
                           dev_info->channel_count > ch) {
                    ch = dev_info->channel_count;
                }
            }
        }
        break;
    }
    if (bw == 0 || ch == 0)
        return -1;
    list_for_each(node_i, &usbmod->usb_card_conf_list) {
        /* Currently only apply the first playback sound card configuration */
        card_info = node_to_item(node_i, struct usb_card_config, list);
        if ((playback && usb_output_device(card_info->usb_device_type)) ||
            (!playback && usb_input_device(card_info->usb_device_type))) {
            usb_get_best_match_for_sample_rate(&card_info->usb_device_conf_list,
                                               bw, ch, sr, &sr,
                                               service_interval,
                                               true);
        }
        break;
    }
    if (sr == 0)
        return -1;
    *bit_width = bw;
    *sample_rate = sr;
    *channel_count = ch;
    return 0;
}

void audio_extn_usb_init(void *adev)
{
    if (usbmod == NULL) {
        usbmod = calloc(1, sizeof(struct usb_module));
        if (usbmod == NULL) {
            ALOGE("%s: error unable to allocate memory", __func__);
            goto exit;
        }
    } else {
        memset(usbmod, 0, sizeof(*usbmod));
    }

    list_init(&usbmod->usb_card_conf_list);
    usbmod->adev = (struct audio_device*)adev;
    usbmod->sidetone_gain = usb_sidetone_gain;
    usbmod->is_capture_supported = false;
exit:
    return;
}

void audio_extn_usb_deinit(void)
{
    if (NULL != usbmod){
        free(usbmod);
        usbmod = NULL;
    }
}
#endif /*USB_HEADSET_ENABLED end*/