/*
 * Copyright (C) 2016 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_utils"
//#define LOG_NDEBUG 0

#include <errno.h>
#include <cutils/properties.h>
#include <cutils/config_utils.h>
#include <stdlib.h>
#include <dlfcn.h>
#include <unistd.h>
#include <cutils/str_parms.h>
#include <log/log.h>
#include <cutils/misc.h>

#include "acdb.h"
#include "audio_hw.h"
#include "platform.h"
#include "platform_api.h"
#include "audio_extn.h"

#define MAX_LENGTH_MIXER_CONTROL_IN_INT 128

static int set_stream_app_type_mixer_ctrl(struct audio_device *adev,
                                          int pcm_device_id, int app_type,
                                          int acdb_dev_id, int sample_rate,
                                          int stream_type,
                                          snd_device_t snd_device)
{

    char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT];
    struct mixer_ctl *ctl;
    int app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT], len = 0, rc = 0;
    int snd_device_be_idx = -1;

    if (stream_type == PCM_PLAYBACK) {
        snprintf(mixer_ctl_name, sizeof(mixer_ctl_name),
             "Audio Stream %d App Type Cfg", pcm_device_id);
    } else if (stream_type == PCM_CAPTURE) {
        snprintf(mixer_ctl_name, sizeof(mixer_ctl_name),
             "Audio Stream Capture %d App Type Cfg", pcm_device_id);
    }

    ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name);
    if (!ctl) {
        ALOGE("%s: Could not get ctl for mixer cmd - %s",
             __func__, mixer_ctl_name);
        rc = -EINVAL;
        goto exit;
    }
    app_type_cfg[len++] = app_type;
    app_type_cfg[len++] = acdb_dev_id;
    app_type_cfg[len++] = sample_rate;

    snd_device_be_idx = platform_get_snd_device_backend_index(snd_device);
    if (snd_device_be_idx > 0)
        app_type_cfg[len++] = snd_device_be_idx;
    ALOGV("%s: stream type %d app_type %d, acdb_dev_id %d "
          "sample rate %d, snd_device_be_idx %d",
          __func__, stream_type, app_type, acdb_dev_id, sample_rate,
          snd_device_be_idx);
    mixer_ctl_set_array(ctl, app_type_cfg, len);

exit:
    return rc;
}

void audio_extn_utils_send_default_app_type_cfg(void *platform, struct mixer *mixer)
{
    int app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT] = {-1};
    int length = 0, app_type = 0,rc = 0;
    struct mixer_ctl *ctl = NULL;
    const char *mixer_ctl_name = "App Type Config";

    ctl = mixer_get_ctl_by_name(mixer, mixer_ctl_name);
    if (!ctl) {
        ALOGE("%s: Could not get ctl for mixer cmd - %s",__func__, mixer_ctl_name);
        return;
    }
    rc = platform_get_default_app_type_v2(platform, PCM_PLAYBACK, &app_type);
    if (rc == 0) {
        app_type_cfg[length++] = 1;
        app_type_cfg[length++] = app_type;
        app_type_cfg[length++] = 48000;
        app_type_cfg[length++] = 16;
        mixer_ctl_set_array(ctl, app_type_cfg, length);
    }
    return;
}

static const char *flags_to_mode(int dir, uint32_t flags)
{
    if (dir == 0) {
        if (flags & AUDIO_OUTPUT_FLAG_VOIP_RX) {
            return "voip";
        }
    } else if (dir == 1) {
        if (flags & AUDIO_INPUT_FLAG_VOIP_TX) {
            return "voip";
        }
    }
    return "default";
}

static int audio_extn_utils_send_app_type_cfg_hfp(struct audio_device *adev,
                                       struct audio_usecase *usecase)
{
    struct mixer_ctl *ctl;
    int pcm_device_id, acdb_dev_id = 0, snd_device = usecase->out_snd_device;
    int32_t sample_rate = DEFAULT_OUTPUT_SAMPLING_RATE;
    int app_type = 0, rc = 0;

    ALOGV("%s", __func__);

    if (usecase->type != PCM_HFP_CALL) {
        ALOGV("%s: not a playback or HFP path, no need to cfg app type", __func__);
        rc = 0;
        goto exit_send_app_type_cfg;
    }
    if ((usecase->id != USECASE_AUDIO_HFP_SCO) &&
        (usecase->id != USECASE_AUDIO_HFP_SCO_WB)) {
        ALOGV("%s: a playback path where app type cfg is not required", __func__);
        rc = 0;
        goto exit_send_app_type_cfg;
    }

    snd_device = usecase->out_snd_device;
    pcm_device_id = platform_get_pcm_device_id(usecase->id, PCM_PLAYBACK);

    acdb_dev_id = platform_get_snd_device_acdb_id(snd_device);
    if (acdb_dev_id < 0) {
        ALOGE("%s: Couldn't get the acdb dev id", __func__);
        rc = -EINVAL;
        goto exit_send_app_type_cfg;
    }

    if (usecase->type == PCM_HFP_CALL) {

        /* config HFP session:1 playback path */
        rc = platform_get_default_app_type_v2(adev->platform, PCM_PLAYBACK, &app_type);
        if (rc < 0)
            goto exit_send_app_type_cfg;

        sample_rate= CODEC_BACKEND_DEFAULT_SAMPLE_RATE;
        rc = set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type,
                                            acdb_dev_id, sample_rate,
                                            PCM_PLAYBACK,
                                            SND_DEVICE_NONE); // use legacy behavior
        if (rc < 0)
            goto exit_send_app_type_cfg;
        /* config HFP session:1 capture path */
        rc = platform_get_default_app_type_v2(adev->platform, PCM_CAPTURE, &app_type);

        if (rc == 0) {
            rc = set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type,
                                                acdb_dev_id, sample_rate,
                                                PCM_CAPTURE,
                                                SND_DEVICE_NONE);
            if (rc < 0)
                goto exit_send_app_type_cfg;
        }
        /* config HFP session:2 capture path */
        pcm_device_id = HFP_ASM_RX_TX;
        snd_device = usecase->in_snd_device;
        acdb_dev_id = platform_get_snd_device_acdb_id(snd_device);
        if (acdb_dev_id <= 0) {
            ALOGE("%s: Couldn't get the acdb dev id", __func__);
            rc = -EINVAL;
            goto exit_send_app_type_cfg;
        }
        rc = platform_get_default_app_type_v2(adev->platform, PCM_CAPTURE, &app_type);
        if (rc == 0) {
            rc = set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type,
                                                acdb_dev_id, sample_rate, PCM_CAPTURE,
                                                SND_DEVICE_NONE);
            if (rc < 0)
                goto exit_send_app_type_cfg;
        }

        /* config HFP session:2 playback path */
        rc = platform_get_default_app_type_v2(adev->platform, PCM_PLAYBACK, &app_type);
        if (rc == 0) {
            rc = set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type,
                                acdb_dev_id, sample_rate,
                                PCM_PLAYBACK, SND_DEVICE_NONE);
            if (rc < 0)
                goto exit_send_app_type_cfg;
        }
    }

    rc = 0;
exit_send_app_type_cfg:
    return rc;
}


static int derive_capture_app_type_cfg(struct audio_device *adev,
                                       struct audio_usecase *usecase,
                                       int *app_type,
                                       int *sample_rate)
{
    if (usecase->stream.in == NULL) {
        return -1;
    }
    struct stream_in *in = usecase->stream.in;
    struct stream_app_type_cfg *app_type_cfg = &in->app_type_cfg;

    *sample_rate = DEFAULT_INPUT_SAMPLING_RATE;
    if (audio_is_usb_in_device(in->device)) {
        platform_check_and_update_copp_sample_rate(adev->platform,
                                                   usecase->in_snd_device,
                                                   in->sample_rate,
                                                   sample_rate);
    }

    app_type_cfg->mode = flags_to_mode(1 /*capture*/, in->flags);
    ALOGV("%s mode %s", __func__, app_type_cfg->mode);
    if (in->format == AUDIO_FORMAT_PCM_16_BIT) {
        platform_get_app_type_v2(adev->platform,
                                 PCM_CAPTURE,
                                 app_type_cfg->mode,
                                 16,
                                 app_type_cfg->sample_rate,
                                 app_type);
    } else if (in->format == AUDIO_FORMAT_PCM_24_BIT_PACKED ||
               in->format == AUDIO_FORMAT_PCM_8_24_BIT) {
        platform_get_app_type_v2(adev->platform,
                                 PCM_CAPTURE,
                                 app_type_cfg->mode,
                                 24,
                                 app_type_cfg->sample_rate,
                                 app_type);
    } else if (in->format == AUDIO_FORMAT_PCM_32_BIT) {
        platform_get_app_type_v2(adev->platform,
                                 PCM_CAPTURE,
                                 app_type_cfg->mode,
                                 32,
                                 app_type_cfg->sample_rate,
                                 app_type);
    } else {
        ALOGE("%s bad format\n", __func__);
        return -1;
    }

    app_type_cfg->app_type = *app_type;
    app_type_cfg->sample_rate = *sample_rate;
    return 0;
}

static int derive_playback_app_type_cfg(struct audio_device *adev,
                                        struct audio_usecase *usecase,
                                        int *app_type,
                                        int *sample_rate)
{
    if (usecase->stream.out == NULL) {
        return -1;
    }
    struct stream_out *out = usecase->stream.out;
    struct stream_app_type_cfg *app_type_cfg = &out->app_type_cfg;

    *sample_rate = DEFAULT_OUTPUT_SAMPLING_RATE;

    // add speaker prot changes if needed
    // and use that to check for device
    if (audio_is_usb_out_device(out->devices)) {
        platform_check_and_update_copp_sample_rate(adev->platform,
                                                   usecase->out_snd_device,
                                                   out->sample_rate,
                                                   sample_rate);
    }

    app_type_cfg->mode = flags_to_mode(0 /*playback*/, out->flags);
    if (!audio_is_linear_pcm(out->format)) {
        platform_get_app_type_v2(adev->platform,
                                 PCM_PLAYBACK,
                                 app_type_cfg->mode,
                                 24,
                                 *sample_rate,
                                 app_type);
        ALOGV("Non pcm got app type %d", *app_type);
    } else if (out->format == AUDIO_FORMAT_PCM_16_BIT) {
        platform_get_app_type_v2(adev->platform,
                                 PCM_PLAYBACK,
                                 app_type_cfg->mode,
                                 16,
                                 *sample_rate,
                                 app_type);
    } else if (out->format == AUDIO_FORMAT_PCM_24_BIT_PACKED ||
               out->format == AUDIO_FORMAT_PCM_8_24_BIT) {
        platform_get_app_type_v2(adev->platform,
                                 PCM_PLAYBACK,
                                 app_type_cfg->mode,
                                 24,
                                 *sample_rate,
                                 app_type);
    } else if (out->format == AUDIO_FORMAT_PCM_32_BIT) {
        platform_get_app_type_v2(adev->platform,
                                 PCM_PLAYBACK,
                                 app_type_cfg->mode,
                                 32,
                                 *sample_rate,
                                 app_type);
    } else {
        ALOGE("%s bad format\n", __func__);
        return -1;
    }

    app_type_cfg->app_type = *app_type;
    app_type_cfg->sample_rate = *sample_rate;
    return 0;
}

static int derive_acdb_dev_id(struct audio_device *adev __unused,
                              struct audio_usecase *usecase)
{
    struct stream_out *out;
    struct stream_in *in;

    if (usecase->type == PCM_PLAYBACK) {
        return platform_get_snd_device_acdb_id(usecase->out_snd_device);
    } else if(usecase->type == PCM_CAPTURE) {
        return platform_get_snd_device_acdb_id(usecase->in_snd_device);
    }
    return -1;
}

int audio_extn_utils_send_app_type_cfg(struct audio_device *adev,
                                       struct audio_usecase *usecase)
{
    int len = 0;
    int sample_rate;
    int app_type;
    int acdb_dev_id;
    size_t app_type_cfg[MAX_LENGTH_MIXER_CONTROL_IN_INT] = {0};
    char mixer_ctl_name[MAX_LENGTH_MIXER_CONTROL_IN_INT] = {0};
    int pcm_device_id;
    struct mixer_ctl *ctl;
    int ret;

    if (usecase->type == PCM_HFP_CALL) {
        return audio_extn_utils_send_app_type_cfg_hfp(adev, usecase);
    }

    if (!platform_supports_app_type_cfg())
        return -1;

    if (usecase->type == PCM_PLAYBACK) {
        ret = derive_playback_app_type_cfg(adev,
                                           usecase,
                                           &app_type,
                                           &sample_rate);
    } else if (usecase->type == PCM_CAPTURE) {
        ret = derive_capture_app_type_cfg(adev,
                                          usecase,
                                          &app_type,
                                          &sample_rate);
    } else {
        ALOGE("%s: Invalid uc type : 0x%x", __func__, usecase->type);
        return -1;
    }

    if (ret < 0) {
        ALOGE("%s: Failed to derive app_type for uc type : 0x%x", __func__,
              usecase->type);
        return -1;
    }

    acdb_dev_id = derive_acdb_dev_id(adev, usecase);
    if (acdb_dev_id <= 0) {
        ALOGE("%s: Couldn't get the acdb dev id", __func__);
        return -1;
    }

    pcm_device_id = platform_get_pcm_device_id(usecase->id, usecase->type);
    set_stream_app_type_mixer_ctrl(adev, pcm_device_id, app_type, acdb_dev_id,
                                   sample_rate,
                                   usecase->type,
                                   usecase->type == PCM_PLAYBACK ? usecase->out_snd_device :
                                                                   usecase->in_snd_device);
    return 0;
}

int audio_extn_utils_send_app_type_gain(struct audio_device *adev,
                                        int app_type,
                                        int *gain)
{
    int gain_cfg[4];
    const char *mixer_ctl_name = "App Type Gain";
    struct mixer_ctl *ctl;
    ctl = mixer_get_ctl_by_name(adev->mixer, mixer_ctl_name);
    if (!ctl) {
        ALOGE("%s: Could not get volume ctl mixer %s", __func__,
              mixer_ctl_name);
        return -EINVAL;
    }
    gain_cfg[0] = 0;
    gain_cfg[1] = app_type;
    gain_cfg[2] = gain[0];
    gain_cfg[3] = gain[1];
    ALOGV("%s app_type %d l(%d) r(%d)", __func__,  app_type, gain[0], gain[1]);
    return mixer_ctl_set_array(ctl, gain_cfg,
                               sizeof(gain_cfg)/sizeof(gain_cfg[0]));
}

// this assumes correct app_type and sample_rate fields
// have been set for the stream using audio_extn_utils_send_app_type_cfg
void audio_extn_utils_send_audio_calibration(struct audio_device *adev,
                                             struct audio_usecase *usecase)
{
    int type = usecase->type;
    int app_type = 0;

    if (type == PCM_PLAYBACK && usecase->stream.out != NULL) {
        struct stream_out *out = usecase->stream.out;
        ALOGV("%s send cal for app_type %d, rate %d", __func__,
              out->app_type_cfg.app_type,
              out->app_type_cfg.sample_rate);
        platform_send_audio_calibration_v2(adev->platform, usecase,
                                           out->app_type_cfg.app_type,
                                           out->app_type_cfg.sample_rate);
    } else if (type == PCM_CAPTURE && usecase->stream.in != NULL) {
        struct stream_in *in = usecase->stream.in;
        ALOGV("%s send cal for capture app_type %d, rate %d", __func__,
              in->app_type_cfg.app_type,
              in->app_type_cfg.sample_rate);
        platform_send_audio_calibration_v2(adev->platform, usecase,
                                           in->app_type_cfg.app_type,
                                           in->app_type_cfg.sample_rate);
    } else {
        /* when app type is default. the sample rate is not used to send cal */
        platform_get_default_app_type_v2(adev->platform, type, &app_type);
        platform_send_audio_calibration_v2(adev->platform, usecase, app_type,
                                           48000);
    }
}

#define MAX_SND_CARD 8
#define RETRY_US 500000
#define RETRY_NUMBER 10

#define min(a, b) ((a) < (b) ? (a) : (b))

static const char *kConfigLocationList[] =
        {"/odm/etc", "/vendor/etc", "/system/etc"};
static const int kConfigLocationListSize =
        (sizeof(kConfigLocationList) / sizeof(kConfigLocationList[0]));

bool audio_extn_utils_resolve_config_file(char file_name[MIXER_PATH_MAX_LENGTH])
{
    char full_config_path[MIXER_PATH_MAX_LENGTH];
    for (int i = 0; i < kConfigLocationListSize; i++) {
        snprintf(full_config_path,
                 MIXER_PATH_MAX_LENGTH,
                 "%s/%s",
                 kConfigLocationList[i],
                 file_name);
        if (F_OK == access(full_config_path, 0)) {
            strcpy(file_name, full_config_path);
            return true;
        }
    }
    return false;
}

/* platform_info_file should be size 'MIXER_PATH_MAX_LENGTH' */
int audio_extn_utils_get_platform_info(const char* snd_card_name, char* platform_info_file)
{
    if (NULL == snd_card_name) {
        return -1;
    }

    struct snd_card_split *snd_split_handle = NULL;
    int ret = 0;
    audio_extn_set_snd_card_split(snd_card_name);
    snd_split_handle = audio_extn_get_snd_card_split();

    snprintf(platform_info_file, MIXER_PATH_MAX_LENGTH, "%s_%s_%s.xml",
                     PLATFORM_INFO_XML_BASE_STRING, snd_split_handle->snd_card,
                     snd_split_handle->form_factor);

    if (!audio_extn_utils_resolve_config_file(platform_info_file)) {
        memset(platform_info_file, 0, MIXER_PATH_MAX_LENGTH);
        snprintf(platform_info_file, MIXER_PATH_MAX_LENGTH, "%s_%s.xml",
                     PLATFORM_INFO_XML_BASE_STRING, snd_split_handle->snd_card);

        if (!audio_extn_utils_resolve_config_file(platform_info_file)) {
            memset(platform_info_file, 0, MIXER_PATH_MAX_LENGTH);
            strlcpy(platform_info_file, PLATFORM_INFO_XML_PATH, MIXER_PATH_MAX_LENGTH);
            ret = audio_extn_utils_resolve_config_file(platform_info_file) ? 0 : -1;
        }
    }

    return ret;
}

int audio_extn_utils_get_snd_card_num()
{

    void *hw_info = NULL;
    struct mixer *mixer = NULL;
    int retry_num = 0;
    int snd_card_num = 0;
    const char* snd_card_name = NULL;
    char platform_info_file[MIXER_PATH_MAX_LENGTH]= {0};

    struct acdb_platform_data *my_data = calloc(1, sizeof(struct acdb_platform_data));

    bool card_verifed[MAX_SND_CARD] = {0};
    const int retry_limit = property_get_int32("audio.snd_card.open.retries", RETRY_NUMBER);

    for (;;) {
        if (snd_card_num >= MAX_SND_CARD) {
            if (retry_num++ >= retry_limit) {
                ALOGE("%s: Unable to find correct sound card, aborting.", __func__);
                snd_card_num = -1;
                goto done;
            }

            snd_card_num = 0;
            usleep(RETRY_US);
            continue;
        }

        if (card_verifed[snd_card_num]) {
            ++snd_card_num;
            continue;
        }

        mixer = mixer_open(snd_card_num);

        if (!mixer) {
            ALOGE("%s: Unable to open the mixer card: %d", __func__,
               snd_card_num);
            ++snd_card_num;
            continue;
        }

        card_verifed[snd_card_num] = true;

        snd_card_name = mixer_get_name(mixer);
        hw_info = hw_info_init(snd_card_name);

        if (audio_extn_utils_get_platform_info(snd_card_name, platform_info_file) < 0) {
            ALOGE("Failed to find platform_info_file");
            goto cleanup;
        }

        /* Initialize snd card name specific ids and/or backends*/
        if (snd_card_info_init(platform_info_file, my_data,
                               &acdb_set_parameters) < 0) {
            ALOGE("Failed to find platform_info_file");
            goto cleanup;
        }

        /* validate the sound card name
         * my_data->snd_card_name can contain
         *     <a> complete sound card name, i.e. <device>-<codec>-<form_factor>-snd-card
         *         example: msm8994-tomtom-mtp-snd-card
         *     <b> or sub string of the card name, i.e. <device>-<codec>
         *         example: msm8994-tomtom
         * snd_card_name is truncated to 32 charaters as per mixer_get_name() implementation
         * so use min of my_data->snd_card_name and snd_card_name length for comparison
         */

        if (my_data->snd_card_name != NULL &&
                strncmp(snd_card_name, my_data->snd_card_name,
                        min(strlen(snd_card_name), strlen(my_data->snd_card_name))) != 0) {
            ALOGI("%s: found valid sound card %s, but not primary sound card %s",
                   __func__, snd_card_name, my_data->snd_card_name);
            goto cleanup;
        }

        ALOGI("%s: found sound card %s, primary sound card expected is %s",
              __func__, snd_card_name, my_data->snd_card_name);
        break;
  cleanup:
        ++snd_card_num;
        mixer_close(mixer);
        mixer = NULL;
        hw_info_deinit(hw_info);
        hw_info = NULL;
    }

done:
    mixer_close(mixer);
    hw_info_deinit(hw_info);

    if (my_data)
        free(my_data);

    return snd_card_num;
}