#include "linux_alsa_core.h"
#include "error.h"
#include <pthread.h>

#define FCX_DEBUG_APP(...)                                                     \
    do {                                                                       \
        printf(__VA_ARGS__);                                                   \
        putchar('\n');                                                         \
    } while (0)
#define AUDIO_DRV_SPK_FS 48000
#define AUDIO_DRV_MIC_FS 48000
#define AUDIO_DRV_SPK_BLK_SZ 1920
#define AUDIO_DRV_MIC_BLK_SZ 1920

// channel = 1   channel=2
// 48000 1920
// 32000  1440
// 16000  1920
// 8000   320

static char *alsa_mix_dev = "default";
static char *alsa_mix_headphone_ctrl = "Headphone";
static char *alsa_mix_digital_ctrl = "Digital";

int nrtc_alsa_configure_device(audio_alsa_core_t *core);

void fcx_mutex_destroy(pthread_mutex_t **mutex) {
    if (!mutex || !*mutex)
        return;
    pthread_mutex_destroy(*mutex);
    free(*mutex);
    mutex = NULL;
}

pthread_mutex_t *fcx_mutex_create() {
    pthread_mutex_t *tmp = NULL;
    tmp = malloc(sizeof(pthread_mutex_t));
    if (tmp)
        pthread_mutex_init(tmp, NULL);

    return tmp;
}

void fcx_mutex_lock(pthread_mutex_t *mutex) { pthread_mutex_lock(mutex); }
void fcx_mutex_unlock(pthread_mutex_t *mutex) { pthread_mutex_unlock(mutex); }

audio_alsa_core_t *nrtc_audio_alsa_core_create(bool input, int channel,
                                               int samplerate, int bit) {
    audio_alsa_core_t *core = malloc(sizeof(audio_alsa_core_t));
    FCX_DEBUG_APP("nrtc_audio_alsa_core_create \n");
    if (core) {
        core->capture_ = input;
        // core->channel_ = 2;
        // core->samplerate_ = 32000;
        // core->bits_per_channel_ = 16;
        // core->bytes_per_frame_ = channel*bit / 8;

        core->channel_ = channel;
        core->samplerate_ = samplerate;
        core->bits_per_channel_ = bit;
        core->bytes_per_frame_ = channel * bit / 8;
        core->pcm_handle_ = NULL;
        core->capturing_ = false;
        core->cache_size_ = 0;

        core->alsa_mixer_.mixer_output_handle_ = NULL;
        core->alsa_mixer_.mixer_output_element_ = NULL;
        core->alsa_mixer_.mixer_input_handle_ = NULL;
        core->alsa_mixer_.mixer_input_element_ = NULL;
        core->alsa_mixer_.mutex_ = fcx_mutex_create();

        return core;
    }
    FCX_DEBUG_APP("nrtc_audio_alsa_core_create end\n");
    return NULL;
}

//打开设备
int nrtc_alsa_open_device(audio_alsa_core_t *core, const char *deviceid) {
    int err = 0;
    char *device = deviceid ? deviceid : "default";
    if (core) {
        if (core->capture_) {
            FCX_DEBUG_APP("nrtc_alsa_open_input_device\n");
            if ((err = snd_pcm_open(&(core->pcm_handle_), device,
                                    SND_PCM_STREAM_CAPTURE, 0)) < 0) {
                FCX_DEBUG_APP("Error in opening audio input device %s: %s",
                              device, snd_strerror(err));
                return -1;
            }
            //打开mixer
            audio_mixer_open_microphone(&(core->alsa_mixer_), device);
        } else {
            FCX_DEBUG_APP("nrtc_alsa_open_input_device\n");
            n_debug("Init dev:%s\n", device);
            if ((err = snd_pcm_open(&(core->pcm_handle_), device,
                                    SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
                FCX_DEBUG_APP("Error in opening audio output device %s: %s",
                              device, snd_strerror(err));
                return -1;
            }
            //打开mixer
            audio_mixer_open_speaker(&(core->alsa_mixer_), device);
        }
        if ((err = nrtc_alsa_configure_device(core)) < 0) {
            FCX_DEBUG_APP("Error in configuring audio output device");
            return -1;
        }

        FCX_DEBUG_APP("Audio output device %s opened", device);
    }
    return 0;
}

//关闭设备
void nrtc_alsa_close_device(audio_alsa_core_t **core) {
    if (core && (*core)) {
        if ((*core)->pcm_handle_) {
            snd_pcm_close((*core)->pcm_handle_);
            if ((*core)->capture_) {
                audio_mixer_close_microphone(&((*core)->alsa_mixer_));
                fcx_mutex_destroy(&(((*core)->alsa_mixer_).mutex_));
                FCX_DEBUG_APP("nrtc_alsa_close_input_device");
            } else {
                audio_mixer_close_speaker(&((*core)->alsa_mixer_));
                fcx_mutex_destroy(&(((*core)->alsa_mixer_).mutex_));
                FCX_DEBUG_APP("nrtc_alsa_close_output_device");
            }
            (*core)->pcm_handle_ = NULL;
        }
        free((*core));
        *core = NULL;
        FCX_DEBUG_APP("Audio device closed");
    }
}

//设置alsa参数
int nrtc_alsa_configure_device(audio_alsa_core_t *core) {
    snd_pcm_t *handle = core->pcm_handle_;
    int m_nChannelsPerFrame = core->channel_;
    int m_nBitsPerChannel = core->bits_per_channel_;
    int m_nBytesPerFrame = core->bytes_per_frame_;
    int nSampleRate = core->samplerate_;
    bool input = core->capture_;
    snd_pcm_hw_params_t *hw_params;
    uint32_t buffer_time = 0;
    //uint32_t period_time = 0;
    unsigned int nFragments = 3;
    uint32_t period_size = 0;
    snd_pcm_uframes_t nFrames =
        (input ? AUDIO_DRV_MIC_BLK_SZ : AUDIO_DRV_SPK_BLK_SZ) /
        m_nBytesPerFrame * nFragments;
    int err = 0;
    snd_pcm_format_t format = m_nBitsPerChannel == 16 ? SND_PCM_FORMAT_S16_LE
                                                      : SND_PCM_FORMAT_UNKNOWN;

    FCX_DEBUG_APP("nrtc_alsa_configure_device "
                  "input->%d,m_nChannelsPerFrame->%d,m_nBitsPerChannel->%d,"
                  "nSampleRate-%d\n",
                  input, m_nChannelsPerFrame, m_nBitsPerChannel, nSampleRate);
    if (!handle)
        return -1;
    /* allocate memory for hardware parameter structure */
    if ((err = snd_pcm_hw_params_malloc(&hw_params)) < 0) {
        FCX_DEBUG_APP(
            "Error in allocating parameter structure for audio %s device: %s",
            input ? "input" : "output", snd_strerror(err));
        return -1;
    }

    /* fill structure from current audio parameters */
    if ((err = snd_pcm_hw_params_any(handle, hw_params)) < 0) {
        FCX_DEBUG_APP(
            "Error in initializing parameter structure for audio %s device: %s",
            input ? "input" : "output", snd_strerror(err));
        return -1;
    }

    /* set access type, sample rate, sample format, channels */
    if ((err = snd_pcm_hw_params_set_access(
             handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        FCX_DEBUG_APP("Error in setting access type for audio %s device: %s",
                      input ? "input" : "output", snd_strerror(err));
        return -1;
    }

    if ((err = snd_pcm_hw_params_set_format(handle, hw_params, format)) < 0) {
        FCX_DEBUG_APP("Error in setting sample format for audio %s device: %s",
                      input ? "input" : "output", snd_strerror(err));
        return -1;
    }
    if ((err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &nSampleRate,
                                               0)) < 0) {
        FCX_DEBUG_APP("Error in setting sample rate for audio %s device: %s",
                      input ? "input" : "output", snd_strerror(err));
        return -1;
    }
    if ((err = snd_pcm_hw_params_set_channels(handle, hw_params,
                                              m_nChannelsPerFrame)) < 0) {
        FCX_DEBUG_APP("Error in setting channel count for audio %s device: %s",
                      input ? "input" : "output", snd_strerror(err));
        return -1;
    }

    if (snd_pcm_hw_params_get_buffer_time_max(hw_params, &buffer_time, 0) < 0) {
        FCX_DEBUG_APP("Error snd_pcm_hw_params_get_buffer_time_max");
        return -1;
    }
    // if (buffer_time > 200000)
    // buffer_time = 200000;
    // period_time = 10000;

    // LOG("period_time: %d,buffer_time %d", period_time,buffer_time);
    // if (snd_pcm_hw_params_set_buffer_time_near(handle, hw_params,
    // &buffer_time, 0) < 0)
    //{
    //	LOG("Error snd_pcm_hw_params_set_buffer_time_near");
    //	return -1;
    //}

    // if (snd_pcm_hw_params_set_period_time_near(handle, hw_params,
    // &period_time, 0) < 0)
    //{
    //	LOG("Error snd_pcm_hw_params_set_period_time_near");
    //	return -1;
    //}

    /* set fragments/periods */
    if ((err = snd_pcm_hw_params_set_periods_near(handle, hw_params,
                                                  &nFragments, 0)) < 0) {
        FCX_DEBUG_APP(
            "Error in setting fragments count to %u for audio %s device: %s",
            nFragments, input ? "input" : "output", snd_strerror(err));
        return -1;
    }

    /* set buffer size in frames */
    if ((err = snd_pcm_hw_params_set_buffer_size_near(handle, hw_params,
                                                      &nFrames)) < 0) {
        FCX_DEBUG_APP(
            "Error in setting buffer size to %u for audio %s device: %s",
            nFrames, input ? "input" : "output", snd_strerror(err));
        return -1;
    }

    /* apply the properties to the device */
    if ((err = snd_pcm_hw_params(handle, hw_params)) < 0) {
        FCX_DEBUG_APP("Error in setting HW params to audio %s device: %s",
                      input ? "input" : "output", snd_strerror(err));
        return -1;
    }

    /*get periods 注意alsa重period_size以frame为单位*/
    if ((err = snd_pcm_hw_params_get_period_size(hw_params, &period_size, 0)) <
        0) {
        FCX_DEBUG_APP(
            "Error snd_pcm_hw_params_get_period_size to audio %s device: %s",
            input ? "input" : "output", snd_strerror(err));
    }

    core->cache_size_ = period_size * core->bytes_per_frame_;
    FCX_DEBUG_APP(
        "snd_pcm_hw_params_get_period_size: period_size->%d,cache_size->%d",
        period_size, core->cache_size_);

    return 0;
}

//获取alsa设备
char *nrtc_alsa_available_devices(bool input) {
    // Device.clear();
    snd_ctl_card_info_t *info;
    snd_pcm_info_t *pcminfo;
    int card = -1;
    int index = 0;
    static char devices[200];
    snd_ctl_card_info_alloca(&info);
    snd_pcm_info_alloca(&pcminfo);

    while (snd_card_next(&card) >= 0 && card >= 0) {
        int err = 0;
        snd_ctl_t *handle;
        char name[20];
        int dev = -1;
        snprintf(name, sizeof(name), "hw:%d", card);
        if ((err = snd_ctl_open(&handle, name, 0)) < 0) {
            continue;
        }

        if ((err = snd_ctl_card_info(handle, info)) < 0) {
            snd_ctl_close(handle);
            continue;
        }

        while (snd_ctl_pcm_next_device(handle, &dev) >= 0 && dev >= 0) {
            snd_pcm_info_set_device(pcminfo, dev);
            snd_pcm_info_set_subdevice(pcminfo, 0);
            snd_pcm_info_set_stream(pcminfo,
                                    input ? SND_PCM_STREAM_CAPTURE
                                          : SND_PCM_STREAM_PLAYBACK);
            if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) {
                continue;
            }

            char szDeviceID[20];
            snprintf(szDeviceID, sizeof(szDeviceID), "plughw:%d,%d", card, dev);
            if (index > 0) {
                devices[index] = '$';
                index += 1;
            }
            memcpy(devices + index, &szDeviceID, strlen(szDeviceID));
            index += strlen(szDeviceID);
        }
        if (index > 0) {
            devices[index] = '\0';
        }
        FCX_DEBUG_APP("devices->%s\n", devices);
        snd_ctl_close(handle);
    }
    if (index <= 0) {
        devices[0] = '\0';
    }
    return devices;
}

int32_t nrtc_alsa_get_devices_info(int32_t function, bool playback,
                                   int32_t enum_device_no,
                                   char *enum_device_name, int32_t edn_len) {
    // Device enumeration based on libjingle implementation
    // by Tristan Schmelcher at Google Inc.

    const char *type = playback ? "Output" : "Input";
    // dmix and dsnoop are only for playback and capture, respectively, but ALSA
    // stupidly includes them in both lists.
    const char *ignorePrefix = playback ? "dsnoop:" : "dmix:";
    // (ALSA lists many more "devices" of questionable interest, but we show
    // them
    // just in case the weird devices may actually be desirable for some
    // users/systems.)

    int err;
    int enumCount = 0;
    bool keepSearching = true;

    // From Chromium issue 95797
    // Loop through the sound cards to get Alsa device hints.
    // Don't use snd_device_name_hint(-1,..) since there is a access violation
    // inside this ALSA API with libasound.so.2.0.0.
    int card = -1;
    while (!snd_card_next(&card) && (card >= 0) && keepSearching) {
        void **hints;
        err = snd_device_name_hint(card, "pcm", &hints);
        if (err != 0) {
            FCX_DEBUG_APP("GetDevicesInfo - device name hint error: %s\n",
                          snd_strerror(err));
            return -1;
        }

        enumCount++; // default is 0
        if ((function == FUNC_GET_DEVICE_NAME ||
             function == FUNC_GET_DEVICE_NAME_FOR_AN_ENUM) &&
            enum_device_no == 0) {
            strcpy(enum_device_name, "default");

            err = snd_device_name_free_hint(hints);
            if (err != 0) {
                FCX_DEBUG_APP(
                    "GetDevicesInfo - device name free hint error: %s\n",
                    snd_strerror(err));
            }

            return 0;
        }
        void **list = NULL;
        for (list = hints; *list != NULL; ++list) {
            char *actualType = snd_device_name_get_hint(*list, "IOID");
            if (actualType) { // NULL means it's both.
                bool wrongType = (strcmp(actualType, type) != 0);
                free(actualType);
                if (wrongType) {
                    // Wrong type of device (i.e., input vs. output).
                    continue;
                }
            }

            char *name = snd_device_name_get_hint(*list, "NAME");
            if (!name) {
                printf("Device has no name");
                // Skip it.
                continue;
            }

            // Now check if we actually want to show this device.
            if (strcmp(name, "default") != 0 && strcmp(name, "null") != 0 &&
                strcmp(name, "pulse") != 0 &&
                strncmp(name, ignorePrefix, strlen(ignorePrefix)) != 0) {
                // Yes, we do.
                char *desc = snd_device_name_get_hint(*list, "DESC");
                if (!desc) {
                    // Virtual devices don't necessarily have descriptions.
                    // Use their names instead.
                    desc = name;
                }

                if (FUNC_GET_NUM_OF_DEVICE == function) {
                    printf("    Enum device %d - %s\n", enumCount, name);
                }
                if ((FUNC_GET_DEVICE_NAME == function) &&
                    (enum_device_no == enumCount)) {
                    // We have found the enum device, copy the name to buffer.
                    strncpy(enum_device_name, desc, edn_len);
                    enum_device_name[edn_len - 1] = '\0';
                    keepSearching = false;
                    // Replace '\n' with '-'.
                    char *pret = strchr(enum_device_name, '\n' /*0xa*/); // LF
                    if (pret)
                        *pret = '-';
                }
                if ((FUNC_GET_DEVICE_NAME_FOR_AN_ENUM == function) &&
                    (enum_device_no == enumCount)) {
                    // We have found the enum device, copy the name to buffer.
                    strncpy(enum_device_name, name, edn_len);
                    enum_device_name[edn_len - 1] = '\0';
                    keepSearching = false;
                }

                if (keepSearching)
                    ++enumCount;

                if (desc != name)
                    free(desc);
            }

            free(name);

            if (!keepSearching)
                break;
        }

        err = snd_device_name_free_hint(hints);
        if (err != 0) {
            printf("GetDevicesInfo - device name free hint error: %s\n",
                   snd_strerror(err));
            // Continue and return true anyway, since we did get the whole list.
        }
    }

    if (FUNC_GET_NUM_OF_DEVICE == function) {
        if (enumCount == 1) // only default?
            enumCount = 0;
        return enumCount; // Normal return point for function 0
    }

    if (keepSearching) {
        // If we get here for function 1 and 2, we didn't find the specified
        // enum device.
        FCX_DEBUG_APP(
            "GetDevicesInfo - Could not find device name or numbers\n");
        return -1;
    }

    return 0;
}

//开始采集
int nrtc_alsa_start_opt(audio_alsa_core_t *core) {
    if (core && core->pcm_handle_) {
        /* drop any output we might got and stop */
        snd_pcm_drop(core->pcm_handle_);
        /* prepare for use */
        snd_pcm_prepare(core->pcm_handle_);
        if (core->capture_)
            core->capturing_ = true;
        return 0;
    }
    return -1;
}

//停止采集
int nrtc_alsa_stop_opt(audio_alsa_core_t *core) {
    if (core && core->pcm_handle_) {
        snd_pcm_drop(core->pcm_handle_);
        if (core->capture_)
            core->capturing_ = false;
        return 0;
    }
    return -1;
}

//往alsa中写入数据
int nrtc_alsa_write_data(audio_alsa_core_t *core, char *data, int nLen) {
    // printf("nrtc_alsa_write_data nLen->%d\n", nLen);
    if (core && core->pcm_handle_) {
        int nFrames = nLen / core->bytes_per_frame_;
        int nTotal = 0;
        while (nFrames > 0) {
            int nWritten = snd_pcm_writei(core->pcm_handle_, data, nFrames);

            if (nWritten == -EAGAIN || (nWritten >= 0 && nWritten < nFrames)) {
                snd_pcm_wait(core->pcm_handle_, 1000);
            } else if (nWritten == -EPIPE) {
                if (snd_pcm_prepare(core->pcm_handle_) < 0)
                    return -1;
            } else if (nWritten == -ESTRPIPE) {
                int err;
                while ((err = snd_pcm_resume(core->pcm_handle_)) == -EAGAIN)
                    sleep(1); /* wait until suspend flag is released */
                if (err < 0) {
                    if (snd_pcm_prepare(core->pcm_handle_) < 0) {
                        return -1;
                    }
                }
            } else if (nWritten < 0) {
                return -1;
            }

            if (nWritten > 0) {
                nTotal += nWritten;
                nFrames -= nWritten;
                data += nWritten * core->bytes_per_frame_;
            }
        }

        return nTotal;
    }
    return -1;
}

//从alsa中读取数据
int nrtc_alsa_read_data(audio_alsa_core_t *core, char *data, int nLen) {
    if (core && core->pcm_handle_) {
        int nFrames = nLen / core->bytes_per_frame_;
        int ret = 0;
        while (nFrames > 0) {
            int nRead = snd_pcm_readi(core->pcm_handle_, data, nFrames);

            if (nRead == -EAGAIN || (nRead >= 0 && nRead < nFrames)) {
                snd_pcm_wait(core->pcm_handle_, 1000);
            } else if (nRead == -EPIPE) {
                if (snd_pcm_prepare(core->pcm_handle_) < 0)
                    return -1;
            } else if (nRead == -ESTRPIPE) {
                int err;
                while ((err = snd_pcm_resume(core->pcm_handle_)) == -EAGAIN)
                    sleep(1); /* wait until suspend flag is released */
                if (err < 0) {
                    if (snd_pcm_prepare(core->pcm_handle_) < 0) {
                        return -1;
                    }
                }
            } else if (nRead < 0) {
                return -1;
            }
            // printf("Frames to read = %d: Frames allready read = %d\n",
            // nFrames, nRead);
            if (nRead > 0) {
                nFrames -= nRead;
                data += nRead * core->bytes_per_frame_;
                ret += nRead * core->bytes_per_frame_;
            }
            // printf("Read Success\n");
        }

        return ret;
    }
    return -1;
}

int nrtc_alsa_set_volume(audio_alsa_core_t *alsa, bool capture, int volume) {
    float rate = 0;
    if (alsa == NULL)
        return -1;
    {
        if (volume > 100)
            volume = 100;
        if (volume < 0)
            volume = 0;
        rate = volume / 100.0;
    }
    if (capture) {
        uint32_t max_microphone_volume =
            audio_mixer_get_max_microphone_volume(&(alsa->alsa_mixer_));
        audio_mixer_set_microphone_volume(&(alsa->alsa_mixer_),
                                          max_microphone_volume * rate);
    } else {
        uint32_t max_speaker_volume =
            audio_mixer_get_max_speaker_volume(&(alsa->alsa_mixer_));
        audio_mixer_set_speaker_volume(&(alsa->alsa_mixer_),
                                       max_speaker_volume * rate);
    }
}

int nrtc_alsa_get_volume(audio_alsa_core_t *alsa, bool capture) {
    if (alsa == NULL)
        return -1;
    float rate = 0;
    if (capture) {
        uint32_t max_microphone_volume =
            audio_mixer_get_max_microphone_volume(&(alsa->alsa_mixer_));
        uint32_t volume =
            audio_mixer_get_microphone_volume(&(alsa->alsa_mixer_));
        rate = (float)volume / max_microphone_volume;
    } else {
        uint32_t max_speaker_volume =
            audio_mixer_get_max_speaker_volume(&(alsa->alsa_mixer_));
        uint32_t volume = audio_mixer_get_speaker_volume(&(alsa->alsa_mixer_));
        rate = (float)volume / max_speaker_volume;
    }
    return rate * 100;
}

//============================================================mixer操作===========================================================
static int32_t
audio_alsa_microphone_mute_is_available(audio_alsa_mixer_t *audio_mixer,
                                        bool *available) {
    int ret = -1;
    if (audio_mixer->mixer_input_element_ == NULL) {
        printf(" no avaliable input mixer element exists\n");
        return -1;
    }
    *available =
        snd_mixer_selem_has_capture_switch(audio_mixer->mixer_input_element_);
    ret = 0;
    return ret;
}

static void audio_alsa_mixer_get_control_name(char *control_name,
                                              char *device_name) {
    char *pos1 = strchr(device_name, ':');
    char *pos2 = strchr(device_name, ',');
    if (!pos2) {
        // Can also be default:CARD=Intel
        pos2 = &device_name[strlen(device_name)];
    }
    if (pos1 && pos2) {
        strcpy(control_name, "hw");
        int nChar = (int)(pos2 - pos1);
        strncpy(&control_name[2], pos1, nChar);
        control_name[2 + nChar] = '\0';
    } else {
        strcpy(control_name, device_name);
    }
}

static int32_t
audio_alsa_mixer_load_speaker_mixer_element(audio_alsa_mixer_t *audio_mixer) {
    int errVal = snd_mixer_load(audio_mixer->mixer_output_handle_);
    if (errVal < 0) {
        FCX_DEBUG_APP("snd_mixer_load(_outputMixerHandle), error: %s\n",
                      snd_strerror(errVal));
        audio_mixer->mixer_output_handle_ = NULL;
        return -1;
    }

    snd_mixer_elem_t *elem = NULL;
    snd_mixer_elem_t *masterElem = NULL;
    snd_mixer_elem_t *speakerElem = NULL;
    unsigned mixerIdx = 0;
    const char *selemName = NULL;

    // Find and store handles to the right mixer elements
    for (elem = snd_mixer_first_elem(audio_mixer->mixer_output_handle_); elem;
         elem = snd_mixer_elem_next(elem), mixerIdx++) {
        if (snd_mixer_selem_is_active(elem)) {
            selemName = snd_mixer_selem_get_name(elem);
            FCX_DEBUG_APP("snd_mixer_selem_get_name %u: %s =%x\n", mixerIdx,
                          selemName, (unsigned int)elem);

            // "Master", "PCM", "Wave", "Master Mono", "PC Speaker", "PCM",
            // "Wave"
            if (strcmp(selemName, "PCM") == 0) {
                audio_mixer->mixer_output_element_ = elem;
                FCX_DEBUG_APP("PCM element set");
            } else if (strcmp(selemName, "Master") == 0) {
                masterElem = elem;
                FCX_DEBUG_APP("Master element found\n");
            } else if (strcmp(selemName, "Speaker") == 0) {
                speakerElem = elem;
                FCX_DEBUG_APP("Speaker element found\n");
            }
        }

        if (audio_mixer->mixer_output_element_) {
            // We have found the element we want
            break;
        }
    }

    // If we didn't find a PCM Handle, use Master or Speaker
    if (audio_mixer->mixer_output_element_ == NULL) {
        if (masterElem != NULL) {
            audio_mixer->mixer_output_element_ = masterElem;
            FCX_DEBUG_APP("Using Master as output volume.\n");
        } else if (speakerElem != NULL) {
            audio_mixer->mixer_output_element_ = speakerElem;
            FCX_DEBUG_APP("Using Speaker as output volume.\n");
        } else {
            audio_mixer->mixer_output_element_ = NULL;
            FCX_DEBUG_APP("Could not find output volume in the mixer.\n");
            return -1;
        }
    }

    return 0;
}

static int32_t
audio_alsa_mixer_load_mic_mixer_element(audio_alsa_mixer_t *audio_mixer) {
    int errVal = snd_mixer_load(audio_mixer->mixer_input_handle_);
    if (errVal < 0) {
        FCX_DEBUG_APP("snd_mixer_load(_inputMixerHandle), error: %s\n",
                      snd_strerror(errVal));
        audio_mixer->mixer_input_handle_ = NULL;
        return -1;
    }

    snd_mixer_elem_t *elem = NULL;
    snd_mixer_elem_t *micElem = NULL;
    unsigned mixerIdx = 0;
    const char *selemName = NULL;

    // Find and store handles to the right mixer elements
    for (elem = snd_mixer_first_elem(audio_mixer->mixer_input_handle_); elem;
         elem = snd_mixer_elem_next(elem), mixerIdx++) {
        if (snd_mixer_selem_is_active(elem)) {
            selemName = snd_mixer_selem_get_name(elem);
            if (strcmp(selemName, "Capture") == 0) // "Capture", "Mic"
            {
                audio_mixer->mixer_input_element_ = elem;
                FCX_DEBUG_APP("Capture element set\n");
            } else if (strcmp(selemName, "Mic") == 0) {
                micElem = elem;
                FCX_DEBUG_APP("Mic element found\n");
            }
        }

        if (audio_mixer->mixer_input_element_) {
            // Use the first Capture element that is found
            // The second one may not work
            break;
        }
    }

    if (audio_mixer->mixer_input_element_ == NULL) {
        // We didn't find a Capture handle, use Mic.
        if (micElem != NULL) {
            audio_mixer->mixer_input_element_ = micElem;
            FCX_DEBUG_APP("Using Mic as capture volume.\n");
        } else {
            audio_mixer->mixer_input_element_ = NULL;
            FCX_DEBUG_APP("Could not find capture volume on the mixer.\n");

            return -1;
        }
    }

    return 0;
}

static int32_t
audio_alsa_mixer_speaker_mute_is_available(audio_alsa_mixer_t *audio_mixer,
                                           bool *available) {
    if (audio_mixer == NULL || audio_mixer->mixer_output_element_ == NULL) {
        FCX_DEBUG_APP("no avaliable output mixer element exists\n");
        return -1;
    }

    *available =
        snd_mixer_selem_has_playback_switch(audio_mixer->mixer_output_element_);

    return 0;
}

//=========================================================================================================
void *audio_alsa_mixer_create(int32_t id) {
    audio_alsa_mixer_t *audio_mixer_ = calloc(1, sizeof(audio_alsa_mixer_t));
    audio_mixer_->id_ = id;
    audio_mixer_->mixer_output_handle_ = NULL;
    audio_mixer_->mixer_output_element_ = NULL;
    audio_mixer_->mixer_input_handle_ = NULL;
    audio_mixer_->mixer_input_element_ = NULL;
    audio_mixer_->mutex_ = fcx_mutex_create();
}
int32_t audio_mixer_open_speaker(audio_alsa_mixer_t *audio_mixer,
                                 char *device_name) {

    FCX_DEBUG_APP("audio_mixer_open_speaker name->%s \n", device_name);

    fcx_mutex_lock(audio_mixer->mutex_);
    int errVal = 0;

    // Close any existing output mixer handle
    //
    if (audio_mixer->mixer_output_handle_ != NULL) {

        FCX_DEBUG_APP("Closing playout mixer\n");

        snd_mixer_free(audio_mixer->mixer_output_handle_);
        if (errVal < 0) {
            FCX_DEBUG_APP("Error freeing playout mixer: %s\n",
                          snd_strerror(errVal));
        }
        errVal = snd_mixer_detach(audio_mixer->mixer_output_handle_,
                                  audio_mixer->mixer_output_str);
        if (errVal < 0) {
            FCX_DEBUG_APP("Error detachinging playout mixer: %s\n",
                          snd_strerror(errVal));
        }
        errVal = snd_mixer_close(audio_mixer->mixer_output_handle_);
        if (errVal < 0) {
            FCX_DEBUG_APP("Error snd_mixer_close(handleMixer) errVal=%d\n",
                          errVal);
        }
    }
    audio_mixer->mixer_output_handle_ = NULL;
    audio_mixer->mixer_output_element_ = NULL;

    errVal = snd_mixer_open(&(audio_mixer->mixer_output_handle_), 0);
    if (errVal < 0) {
        FCX_DEBUG_APP("snd_mixer_open(&_outputMixerHandle, 0) - error\n");
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }

    char controlName[kAdmMaxDeviceNameSize] = {0};
    audio_alsa_mixer_get_control_name(controlName, device_name);

    FCX_DEBUG_APP("snd_mixer_attach(_outputMixerHandle, %s)\n", controlName);

    errVal = snd_mixer_attach(audio_mixer->mixer_output_handle_, controlName);
    if (errVal < 0) {
        FCX_DEBUG_APP("snd_mixer_attach(_outputMixerHandle, %s) error: %s\n",
                      controlName, snd_strerror(errVal));
        audio_mixer->mixer_output_handle_ = NULL;
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }
    strcpy(audio_mixer->mixer_output_str, controlName);

    errVal =
        snd_mixer_selem_register(audio_mixer->mixer_output_handle_, NULL, NULL);
    if (errVal < 0) {
        FCX_DEBUG_APP("snd_mixer_selem_register(_outputMixerHandle,"
                      " NULL, NULL), error: %s\n",
                      snd_strerror(errVal));
        audio_mixer->mixer_output_handle_ = NULL;
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }

    // Load and find the proper mixer element
    if (audio_alsa_mixer_load_speaker_mixer_element(audio_mixer) < 0) {
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }

    if (audio_mixer->mixer_output_handle_ != NULL) {
        FCX_DEBUG_APP("  the output mixer device is now open (0x%x)\n",
                      (unsigned int)audio_mixer->mixer_output_handle_);
    }
    fcx_mutex_unlock(audio_mixer->mutex_);
    return 0;
}

int32_t audio_mixer_open_microphone(audio_alsa_mixer_t *audio_mixer,
                                    char *device_name) {
    n_debug("audio_mixer_open_microphone name->%s \n", device_name);

    fcx_mutex_lock(audio_mixer->mutex_);
    int errVal = 0;

    // Close any existing input mixer handle
    //
    if (audio_mixer->mixer_input_handle_ != NULL) {
        FCX_DEBUG_APP("Closing record mixer\n");
        snd_mixer_free(audio_mixer->mixer_input_handle_);
        if (errVal < 0) {
            FCX_DEBUG_APP("Error freeing record mixer: %s\n",
                          snd_strerror(errVal));
        }
        FCX_DEBUG_APP("Closing record mixer\n");

        errVal = snd_mixer_detach(audio_mixer->mixer_input_handle_,
                                  audio_mixer->mixer_input_str);
        if (errVal < 0) {
            FCX_DEBUG_APP("Error detachinging record mixer: %s\n",
                          snd_strerror(errVal));
        }
        FCX_DEBUG_APP("Closing record mixer\n");

        errVal = snd_mixer_close(audio_mixer->mixer_input_handle_);
        if (errVal < 0) {
            FCX_DEBUG_APP("Error snd_mixer_close(handleMixer) errVal=%d\n",
                          errVal);
        }
        FCX_DEBUG_APP("Closing record mixer\n");
    }
    audio_mixer->mixer_input_handle_ = NULL;
    audio_mixer->mixer_input_element_ = NULL;

    errVal = snd_mixer_open(&(audio_mixer->mixer_input_handle_), 0);
    if (errVal < 0) {
        FCX_DEBUG_APP("snd_mixer_open(&_inputMixerHandle, 0)- error\n");
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }

    char controlName[kAdmMaxDeviceNameSize] = {0};
    audio_alsa_mixer_get_control_name(controlName, device_name);

    FCX_DEBUG_APP("snd_mixer_attach(_inputMixerHandle, %s)\n", controlName);

    errVal = snd_mixer_attach(audio_mixer->mixer_input_handle_, controlName);
    if (errVal < 0) {
        FCX_DEBUG_APP("snd_mixer_attach(_inputMixerHandle, %s) error: %s\n",
                      controlName, snd_strerror(errVal));
        audio_mixer->mixer_input_handle_ = NULL;
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }
    strcpy(audio_mixer->mixer_input_str, controlName);

    errVal =
        snd_mixer_selem_register(audio_mixer->mixer_input_handle_, NULL, NULL);
    if (errVal < 0) {
        FCX_DEBUG_APP("snd_mixer_selem_register(_inputMixerHandle,"
                      " NULL, NULL), error: %s\n",
                      snd_strerror(errVal));

        audio_mixer->mixer_input_handle_ = NULL;
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }
    // Load and find the proper mixer element
    if (audio_alsa_mixer_load_mic_mixer_element(audio_mixer) < 0) {
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }

    if (audio_mixer->mixer_input_handle_ != NULL) {
        FCX_DEBUG_APP("the input mixer device is now open (0x%x)\n",
                      (unsigned int)audio_mixer->mixer_input_handle_);
    }
    fcx_mutex_unlock(audio_mixer->mutex_);
    return 0;
}

int32_t audio_mixer_set_speaker_volume(audio_alsa_mixer_t *audio_mixer,
                                       uint32_t volume) {
    FCX_DEBUG_APP("audio_mixer_set_speaker_volume (volume->%u)\n", volume);
    int ret = -1;
    fcx_mutex_lock(audio_mixer->mutex_);

    if (audio_mixer->mixer_output_element_ == NULL) {
        FCX_DEBUG_APP("no avaliable output mixer element exists\n");
        ret = -1;
    } else {
        int errVal = snd_mixer_selem_set_playback_volume_all(
            audio_mixer->mixer_output_element_, volume);
        if (errVal < 0) {
            FCX_DEBUG_APP("Error changing master volume: %s\n",
                          snd_strerror(errVal));
            ret = -1;
        }
    }
    fcx_mutex_unlock(audio_mixer->mutex_);
    return ret;
}

uint32_t audio_mixer_get_speaker_volume(audio_alsa_mixer_t *audio_mixer) {
    uint32_t volume = 0;
    if (audio_mixer == NULL || audio_mixer->mixer_output_element_ == NULL) {
        FCX_DEBUG_APP("no avaliable output mixer element exists\n");
        return 0;
    }

    long int vol = 0;
    int errVal = snd_mixer_selem_get_playback_volume(
        audio_mixer->mixer_output_element_, (snd_mixer_selem_channel_id_t)0,
        &vol);
    if (errVal < 0) {
        FCX_DEBUG_APP("Error getting outputvolume: %s\n", snd_strerror(errVal));
        return 0;
    }
    FCX_DEBUG_APP("audio_mixer_get_speaker_volume  vol->%ld\n", vol);

    volume = (uint32_t)vol;

    return volume;
}

uint32_t audio_mixer_get_max_speaker_volume(audio_alsa_mixer_t *audio_mixer) {
    uint32_t max_vol = 0;
    if (audio_mixer == NULL || audio_mixer->mixer_output_element_ == NULL) {
        FCX_DEBUG_APP("no avilable output mixer element exists\n");
        return -1;
    }

    long int minVol = 0;
    long int maxVol = 0;

    int errVal = snd_mixer_selem_get_playback_volume_range(
        audio_mixer->mixer_output_element_, &minVol, &maxVol);

    FCX_DEBUG_APP("Playout hardware volume range, min: %ld, max: %ld\n", minVol,
                  maxVol);

    if (maxVol <= minVol) {
        FCX_DEBUG_APP("Error getting get_playback_volume_range: %s\n",
                      snd_strerror(errVal));
    }

    max_vol = (uint32_t)(maxVol);

    return max_vol;
}

uint32_t audio_mixer_get_min_speaker_volume(audio_alsa_mixer_t *audio_mixer) {
    uint32_t min_vol = 0;
    if (audio_mixer == NULL || audio_mixer->mixer_output_element_ == NULL) {
        FCX_DEBUG_APP("no avilable output mixer element exists\n");
        return -1;
    }

    long int minVol = 0;
    long int maxVol = 0;

    int errVal = snd_mixer_selem_get_playback_volume_range(
        audio_mixer->mixer_output_element_, &minVol, &maxVol);

    FCX_DEBUG_APP("Playout hardware volume range, min: %ld, max: %ld\n", minVol,
                  maxVol);

    if (maxVol <= minVol) {
        FCX_DEBUG_APP("Error getting get_playback_volume_range: %s\n",
                      snd_strerror(errVal));
    }

    min_vol = (uint32_t)(minVol);

    return min_vol;
}

int32_t audio_mixer_set_speaker_mute(audio_alsa_mixer_t *audio_mixer,
                                     bool mute) {
    FCX_DEBUG_APP("audio_mixer_set_speaker_mute (enable->%u)\n", mute);

    fcx_mutex_lock(audio_mixer->mutex_);

    if (audio_mixer->mixer_output_element_ == NULL) {
        FCX_DEBUG_APP("no avaliable output mixer element exists\n");
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }

    // Ensure that the selected speaker destination has a valid mute control.
    bool available = false;
    audio_alsa_mixer_speaker_mute_is_available(audio_mixer, &available);
    if (!available) {
        FCX_DEBUG_APP("it is not possible to mute the speaker\n");
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }

    // Note value = 0 (off) means muted
    int errVal = snd_mixer_selem_set_playback_switch_all(
        audio_mixer->mixer_output_element_, !mute);
    if (errVal < 0) {
        FCX_DEBUG_APP("Error setting playback switch: %s\n",
                      snd_strerror(errVal));
        fcx_mutex_unlock(audio_mixer->mutex_);
        return -1;
    }
    fcx_mutex_unlock(audio_mixer->mutex_);
    return (0);
}

bool audio_mixer_get_speaker_mute(audio_alsa_mixer_t *audio_mixer) {

    if (audio_mixer == NULL || audio_mixer->mixer_output_element_ == NULL) {
        FCX_DEBUG_APP("  no avaliable output mixer exists\n");
        return false;
    }

    // Ensure that the selected speaker destination has a valid mute control.
    bool available =
        snd_mixer_selem_has_playback_switch(audio_mixer->mixer_output_element_);
    if (!available) {
        FCX_DEBUG_APP("it is not possible to mute the speaker\n");
        return false;
    }

    int value = false;

    // Retrieve one boolean control value for a specified mute-control
    //
    int errVal = snd_mixer_selem_get_playback_switch(
        audio_mixer->mixer_output_element_, (snd_mixer_selem_channel_id_t)0,
        &value);
    if (errVal < 0) {
        FCX_DEBUG_APP("Error getting playback switch: %s\n",
                      snd_strerror(errVal));
        return false;
    }

    // Note value = 0 (off) means muted
    return (bool)!value;
}

int32_t audio_mixer_set_microphone_mute(audio_alsa_mixer_t *audio_mixer,
                                        bool mute) {
    int ret = -1;
    bool availabe = false;
    if (audio_mixer == NULL || audio_mixer->mixer_input_element_ == NULL)
        return ret;

    fcx_mutex_lock(audio_mixer->mutex_);
    audio_alsa_microphone_mute_is_available(audio_mixer, &availabe);
    if (!availabe) {
        FCX_DEBUG_APP("it is not possible to mute the microphone\n");
        ret = -1;
    } else {
        int errVal = snd_mixer_selem_set_capture_switch_all(
            audio_mixer->mixer_input_element_, !mute);
        if (errVal < 0) {
            FCX_DEBUG_APP("Error setting capture switch: %s\n",
                          snd_strerror(errVal));
            ret = -1;
        }
    }

    fcx_mutex_unlock(audio_mixer->mutex_);
    return ret;
}

bool audio_mixer_get_microphone_mute(audio_alsa_mixer_t *audio_mixer) {
    if (audio_mixer == NULL || audio_mixer->mixer_input_element_ == NULL) {
        FCX_DEBUG_APP(" no avaliable input mixer exists\n");
        return false;
    }
    // Ensure that the selected microphone destination has a valid mute control.
    bool available =
        snd_mixer_selem_has_capture_switch(audio_mixer->mixer_input_element_);
    if (!available) {
        FCX_DEBUG_APP("  it is not possible to mute the microphone\n");
        return false;
    }

    int value = false;

    // Retrieve one boolean control value for a specified mute-control
    //
    int errVal = snd_mixer_selem_get_capture_switch(
        audio_mixer->mixer_input_element_, (snd_mixer_selem_channel_id_t)0,
        &value);
    if (errVal < 0) {
        FCX_DEBUG_APP("Error getting capture switch: %s\n",
                      snd_strerror(errVal));
        return false;
    }

    // Note value = 0 (off) means muted
    return (!value);
}

int32_t audio_mixer_set_microphone_volume(audio_alsa_mixer_t *audio_mixer,
                                          uint32_t volume) {
    int32_t ret = -1;
    if (audio_mixer == NULL || audio_mixer->mixer_input_element_ == NULL) {
        FCX_DEBUG_APP(" no avaliable input mixer exists\n");
        return false;
    }
    fcx_mutex_lock(audio_mixer->mutex_);
    int errVal = snd_mixer_selem_set_capture_volume_all(
        audio_mixer->mixer_input_element_, volume);
    if (errVal < 0) {
        FCX_DEBUG_APP("Error changing microphone volume: %s\n",
                      snd_strerror(errVal));
        ret = -1;
    }
    ret = 0;
    fcx_mutex_unlock(audio_mixer->mutex_);
    return ret;
}

uint32_t audio_mixer_get_microphone_volume(audio_alsa_mixer_t *audio_mixer) {
    uint32_t volume = 0;
    if (audio_mixer == NULL || audio_mixer->mixer_input_element_ == NULL) {
        FCX_DEBUG_APP(" no avaliable input mixer exists\n");
        return false;
    }

    long int vol = 0;
    int errVal = snd_mixer_selem_get_capture_volume(
        audio_mixer->mixer_input_element_, (snd_mixer_selem_channel_id_t)0,
        &vol);
    if (errVal < 0) {
        FCX_DEBUG_APP("Error getting inputvolume: %s\n", snd_strerror(errVal));
        return -1;
    }
    FCX_DEBUG_APP("audio_mixer_get_microphone_volume => vol=%ld\n", vol);

    volume = (uint32_t)vol;

    return volume;
}

uint32_t
audio_mixer_get_max_microphone_volume(audio_alsa_mixer_t *audio_mixer) {
    if (audio_mixer == NULL || audio_mixer->mixer_input_element_ == NULL) {
        FCX_DEBUG_APP("no avaliable input mixer element exists\n");
        return -1;
    }

    long int minVol = 0;
    long int maxVol = 0;

    // check if we have mic volume at all
    if (!snd_mixer_selem_has_capture_volume(
            audio_mixer->mixer_input_element_)) {
        FCX_DEBUG_APP("No microphone volume available\n");
        return -1;
    }

    int errVal = snd_mixer_selem_get_capture_volume_range(
        audio_mixer->mixer_input_element_, &minVol, &maxVol);

    FCX_DEBUG_APP("Microphone hardware volume range, min: %ld, max: %ld\n",
                  minVol, maxVol);
    if (maxVol <= minVol) {
        FCX_DEBUG_APP("Error getting microphone volume range: %s\n",
                      snd_strerror(errVal));
    }

    return maxVol;
}

uint32_t
audio_mixer_get_min_microphone_volume(audio_alsa_mixer_t *audio_mixer) {
    if (audio_mixer == NULL || audio_mixer->mixer_input_element_ == NULL) {
        FCX_DEBUG_APP("no avaliable input mixer element exists\n");
        return -1;
    }

    long int minVol = 0;
    long int maxVol = 0;

    // check if we have mic volume at all
    if (!snd_mixer_selem_has_capture_volume(
            audio_mixer->mixer_input_element_)) {
        FCX_DEBUG_APP("No microphone volume available\n");
        return -1;
    }

    int errVal = snd_mixer_selem_get_capture_volume_range(
        audio_mixer->mixer_input_element_, &minVol, &maxVol);

    FCX_DEBUG_APP("Microphone hardware volume range, min: %ld, max: %ld\n",
                  minVol, maxVol);
    if (maxVol <= minVol) {
        FCX_DEBUG_APP("Error getting microphone volume range: %s\n",
                      snd_strerror(errVal));
    }

    return minVol;
}

int32_t audio_mixer_close(audio_alsa_mixer_t *audio_mixer) {
    int ret = -1;
    if (audio_mixer == NULL)
        return ret;
    ret = 0;
    fcx_mutex_lock(audio_mixer->mutex_);
    audio_mixer_close_speaker(audio_mixer);
    audio_mixer_close_microphone(audio_mixer);
    fcx_mutex_unlock(audio_mixer->mutex_);
    return ret;
}

int32_t audio_mixer_close_speaker(audio_alsa_mixer_t *audio_mixer) {
    int errVal = -1;
    if (audio_mixer == NULL)
        return errVal;

    fcx_mutex_lock(audio_mixer->mutex_);
    if (audio_mixer->mixer_output_handle_) {
        snd_mixer_free(audio_mixer->mixer_output_handle_);
        errVal = snd_mixer_detach(audio_mixer->mixer_output_handle_,
                                  audio_mixer->mixer_output_str);
        if (errVal < 0) {
            FCX_DEBUG_APP(" Error detachinging playout mixer: %s\n",
                          snd_strerror(errVal));
        }
        errVal = snd_mixer_close(audio_mixer->mixer_output_handle_);
        if (errVal < 0) {
            FCX_DEBUG_APP(" Error snd_mixer_close(handleMixer) errVal=%d\n",
                          errVal);
        }
        audio_mixer->mixer_output_handle_ = NULL;
        audio_mixer->mixer_output_element_ = NULL;
    }
    memset(audio_mixer->mixer_output_str, 0, kAdmMaxDeviceNameSize);
    fcx_mutex_unlock(audio_mixer->mutex_);
}

int32_t audio_mixer_close_microphone(audio_alsa_mixer_t *audio_mixer) {
    int errVal = -1;
    if (audio_mixer == NULL)
        return errVal;

    fcx_mutex_lock(audio_mixer->mutex_);
    if (audio_mixer->mixer_input_handle_) {
        snd_mixer_free(audio_mixer->mixer_input_handle_);
        errVal = snd_mixer_detach(audio_mixer->mixer_input_handle_,
                                  audio_mixer->mixer_input_str);
        if (errVal < 0) {
            FCX_DEBUG_APP(" Error detachinging playout mixer: %s\n",
                          snd_strerror(errVal));
        }
        errVal = snd_mixer_close(audio_mixer->mixer_input_handle_);
        if (errVal < 0) {
            FCX_DEBUG_APP(" Error snd_mixer_close(handleMixer) errVal=%d\n",
                          errVal);
        }
        audio_mixer->mixer_input_handle_ = NULL;
        audio_mixer->mixer_input_element_ = NULL;
    }
    memset(audio_mixer->mixer_input_str, 0, kAdmMaxDeviceNameSize);
    fcx_mutex_unlock(audio_mixer->mutex_);
}

void audio_alsa_mixer_destroy(audio_alsa_mixer_t **audio_mixer) {
    if (audio_mixer != NULL && *audio_mixer != NULL) {
        audio_mixer_close(*audio_mixer);
        fcx_mutex_destroy(&((*audio_mixer)->mutex_));
        free(*audio_mixer);
        *audio_mixer = NULL;
    }
}
