/*
 * Copyright (C) 2014 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 "ext_speaker"
/*#define LOG_NDEBUG 0*/
#include <cutils/log.h>

#include <stdlib.h>
#include <audio_hw.h>
#include <dlfcn.h>

#ifdef __LP64__
#define LIB_SPEAKER_BUNDLE "/system/lib64/soundfx/libspeakerbundle.so"
#else
#define LIB_SPEAKER_BUNDLE "/system/lib/soundfx/libspeakerbundle.so"
#endif

typedef void (*set_mode_t)(int);
typedef void (*set_speaker_on_t)(bool);
typedef void (*set_earpiece_on_t)(bool);
typedef void (*set_voice_vol_t)(float);

struct speaker_data {
    struct audio_device *adev;
    void *speaker_bundle;
    set_mode_t set_mode;
    set_speaker_on_t set_speaker_on;
    set_earpiece_on_t set_earpiece_on;
    set_voice_vol_t set_voice_vol;
};

static struct speaker_data* open_speaker_bundle()
{
    struct speaker_data *sd = calloc(1, sizeof(struct speaker_data));

    sd->speaker_bundle = dlopen(LIB_SPEAKER_BUNDLE, RTLD_NOW);
    if (sd->speaker_bundle == NULL) {
        ALOGE("%s: DLOPEN failed for %s", __func__, LIB_SPEAKER_BUNDLE);
        goto error;
    } else {
        ALOGV("%s: DLOPEN successful for %s", __func__, LIB_SPEAKER_BUNDLE);

        sd->set_mode = (set_mode_t)dlsym(sd->speaker_bundle,
                                             "set_mode");
        if (sd->set_mode == NULL) {
            ALOGE("%s: dlsym error %s for set_mode", __func__,
                  dlerror());
            goto error;
        }
        sd->set_speaker_on = (set_speaker_on_t)dlsym(sd->speaker_bundle,
                                             "set_speaker_on");
        if (sd->set_speaker_on == NULL) {
            ALOGE("%s: dlsym error %s for set_speaker_on", __func__,
                  dlerror());
            goto error;
        }
        sd->set_earpiece_on = (set_earpiece_on_t)dlsym(sd->speaker_bundle,
                                             "set_earpiece_on");
        if (sd->set_earpiece_on == NULL) {
            ALOGE("%s: dlsym error %s for set_earpiece_on", __func__,
                  dlerror());
            goto error;
        }
        sd->set_voice_vol = (set_voice_vol_t)dlsym(sd->speaker_bundle,
                                             "set_voice_volume");
        if (sd->set_voice_vol == NULL) {
            ALOGE("%s: dlsym error %s for set_voice_volume",
                  __func__, dlerror());
            goto error;
        }
    }
    return sd;

error:
    free(sd);
    return 0;
}

static void close_speaker_bundle(struct speaker_data *sd)
{
    if (sd != NULL) {
        dlclose(sd->speaker_bundle);
        free(sd);
        sd = NULL;
    }
}

void *audio_extn_extspk_init(struct audio_device *adev)
{
    struct speaker_data *data = open_speaker_bundle();

    if (data)
        data->adev = adev;

    return data;
}

void audio_extn_extspk_deinit(void *extn)
{
    struct speaker_data *data = (struct speaker_data*)extn;
    close_speaker_bundle(data);
}

void audio_extn_extspk_update(void* extn)
{
    struct speaker_data *data = (struct speaker_data*)extn;

    if (data) {
        bool speaker_on = false;
        bool earpiece_on = false;
        struct listnode *node;
        struct audio_usecase *usecase;
        list_for_each(node, &data->adev->usecase_list) {
            usecase = node_to_item(node, struct audio_usecase, list);
            if (usecase->devices & AUDIO_DEVICE_OUT_EARPIECE) {
                if(data->adev->snd_dev_ref_cnt[usecase->out_snd_device] != 0) {
                    earpiece_on = true;
                }
            }
            if (usecase->devices & AUDIO_DEVICE_OUT_SPEAKER) {
                if(data->adev->snd_dev_ref_cnt[usecase->out_snd_device] != 0) {
                    speaker_on = true;
                }
            }
        }
        data->set_earpiece_on(earpiece_on);
        data->set_speaker_on(speaker_on);
    }
}

void audio_extn_extspk_set_mode(void* extn, audio_mode_t mode)
{
    struct speaker_data *data = (struct speaker_data*)extn;

    if (data)
        data->set_mode(mode);
}

void audio_extn_extspk_set_voice_vol(void* extn, float vol)
{
    struct speaker_data *data = (struct speaker_data*)extn;

    if (data)
        data->set_voice_vol(vol);
}