/*
* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials provided
*       with the distribution.
*     * Neither the name of The Linux Foundation nor the names of its
*       contributors may be used to endorse or promote products derived
*       from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
* ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#define LOG_TAG "qahw_effect"
//#define LOG_NDEBUG 0
#define LOG_NDDEBUG 0

#include <cutils/list.h>
#include <dlfcn.h>
#include <utils/Log.h>
#include <hardware/audio.h>
#include <hardware/audio_effect.h>
#include <stdlib.h>

#include "qahw_effect_api.h"

// The current effect API version.
#define QAHW_EFFECT_API_VERSION_CURRENT QAHW_EFFECT_API_VERSION_0_0
#define PATH_MAX 4096

typedef struct {
    char                   lib_path[PATH_MAX];
    pthread_mutex_t        lock;
    uint32_t               ref_count;
    audio_effect_library_t *desc;
    void                   *handle;
    struct listnode        lib_list;
} qahw_effect_lib_t;

// list of loaded effect libraries
static struct listnode effect_libraries_list;
static int effect_libraries_count = 0;
static pthread_mutex_t effect_libraries_lock = PTHREAD_MUTEX_INITIALIZER;

qahw_effect_lib_t *get_qahw_effect_lib_by_name(const char *lib_path) {
    struct listnode *node = NULL;
    qahw_effect_lib_t *effect_lib = NULL, *effect_lib_temp = NULL;

    if (lib_path == NULL)
        goto exit;

    list_for_each(node, &effect_libraries_list) {
        effect_lib_temp = node_to_item(node, qahw_effect_lib_t, lib_list);
        if(!strncmp(lib_path, effect_lib_temp->lib_path, PATH_MAX)) {
            effect_lib = effect_lib_temp;
            break;
        }
    }
exit:
    return effect_lib;
}


qahw_effect_lib_t *get_qahw_effect_lib_by_desc(qahw_effect_lib_handle_t handle) {
    struct listnode *node = NULL;
    qahw_effect_lib_t *effect_lib = NULL, *effect_lib_temp = NULL;

    if (handle == NULL)
        goto exit;

    list_for_each(node, &effect_libraries_list) {
        effect_lib_temp = node_to_item(node, qahw_effect_lib_t, lib_list);
        if (effect_lib_temp->desc == (audio_effect_library_t *)handle) {
            effect_lib = effect_lib_temp;
            break;
        }
    }
exit:
    return effect_lib;
}


qahw_effect_lib_handle_t qahw_effect_load_library(const char *lib_path) {
    audio_effect_library_t *desc;
    qahw_effect_lib_t      *qahw_effect_lib;
    void                   *handle;

    if (strlen(lib_path) >= PATH_MAX -1) {
        ALOGE("%s: effect libraries path too long", __func__);
        return NULL;
    }

    /* return existing lib handler if already loaded */
    pthread_mutex_lock(&effect_libraries_lock);
    if (effect_libraries_count > 0) {
        qahw_effect_lib = get_qahw_effect_lib_by_name(lib_path);
        if (qahw_effect_lib != NULL) {
            desc = qahw_effect_lib->desc;
            pthread_mutex_lock(&qahw_effect_lib->lock);
            qahw_effect_lib->ref_count++;
            pthread_mutex_unlock(&qahw_effect_lib->lock);
            goto done;
        }
    }

    handle = dlopen(lib_path, RTLD_NOW);
    if (handle == NULL) {
        ALOGE("%s: failed to dlopen lib %s", __func__, lib_path);
        goto error;
    }

    desc = (audio_effect_library_t *)dlsym(handle, AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR);
    if (desc == NULL) {
        ALOGE("%s: could not find symbol %s", __func__, AUDIO_EFFECT_LIBRARY_INFO_SYM_AS_STR);
        goto error;
    }

    if (AUDIO_EFFECT_LIBRARY_TAG != desc->tag) {
        ALOGE("%s: bad tag %08x in lib info struct", __func__, desc->tag);
        goto error;
    }

    if (EFFECT_API_VERSION_MAJOR(desc->version) !=
            EFFECT_API_VERSION_MAJOR(EFFECT_LIBRARY_API_VERSION)) {
        ALOGE("%s: bad lib version %08x", __func__, desc->version);
        goto error;
    }

    qahw_effect_lib = (qahw_effect_lib_t *)calloc(1, sizeof(qahw_effect_lib_t));
    if (qahw_effect_lib == NULL) {
        ALOGE("%s: calloc failed", __func__);
        goto error;
    }

    if (!effect_libraries_count)
        list_init(&effect_libraries_list);
    effect_libraries_count++;

    /* init and load effect lib into global list */
    strlcpy(qahw_effect_lib->lib_path, lib_path, PATH_MAX);
    pthread_mutex_init(&qahw_effect_lib->lock, (const pthread_mutexattr_t *) NULL);
    qahw_effect_lib->ref_count = 1;
    qahw_effect_lib->desc = desc;
    qahw_effect_lib->handle = handle;

    list_add_tail(&effect_libraries_list, &qahw_effect_lib->lib_list);

done:
    pthread_mutex_unlock(&effect_libraries_lock);
    return (qahw_effect_lib_handle_t)desc;

error:
    if (handle != NULL)
        dlclose(handle);

    pthread_mutex_unlock(&effect_libraries_lock);
    return NULL;
}


int32_t qahw_effect_unload_library(qahw_effect_lib_handle_t handle) {
    qahw_effect_lib_t *qahw_effect_lib;

    pthread_mutex_lock(&effect_libraries_lock);
    if (effect_libraries_count <= 0) {
        ALOGW("%s: no valid libraries loaded", __func__);
        pthread_mutex_unlock(&effect_libraries_lock);
        return -EINVAL;
    }

    qahw_effect_lib = get_qahw_effect_lib_by_desc(handle);
    if (qahw_effect_lib == NULL) {
        ALOGW("%s: effect lib handle(%p) not in loaded queue", __func__, (void *)handle);
        pthread_mutex_unlock(&effect_libraries_lock);
        return -EINVAL;
    }

    pthread_mutex_lock(&qahw_effect_lib->lock);
    qahw_effect_lib->ref_count--;
    if (qahw_effect_lib->ref_count > 0) {
        ALOGW("%s: skip unloading effect lib, ref count %d", __func__, qahw_effect_lib->ref_count);
        pthread_mutex_unlock(&qahw_effect_lib->lock);
        goto done;
    }

    if (qahw_effect_lib->handle)
        dlclose(qahw_effect_lib->handle);
    effect_libraries_count--;
    list_remove(&qahw_effect_lib->lib_list);
    pthread_mutex_unlock(&qahw_effect_lib->lock);
    pthread_mutex_destroy(&qahw_effect_lib->lock);
    free(qahw_effect_lib);

done:
    pthread_mutex_unlock(&effect_libraries_lock);
    return 0;
}


int32_t qahw_effect_create(qahw_effect_lib_handle_t handle,
                           const qahw_effect_uuid_t *uuid,
                           int32_t io_handle,
                           qahw_effect_handle_t *effect_handle) {
    int32_t rc = -EINVAL;
    audio_effect_library_t *desc = (audio_effect_library_t *)handle;

    if (desc != NULL) {
        rc = desc->create_effect((const effect_uuid_t *)uuid, 0, io_handle,
                (effect_handle_t *)effect_handle);
    }

    return rc;
}


int32_t qahw_effect_release(qahw_effect_lib_handle_t handle,
                            qahw_effect_handle_t effect_handle) {
    int32_t rc = -EINVAL;
    audio_effect_library_t *desc = (audio_effect_library_t *)handle;

    if (desc != NULL) {
        rc = desc->release_effect((effect_handle_t)effect_handle);
    }

    return rc;
}


int32_t qahw_effect_get_descriptor(qahw_effect_lib_handle_t handle,
                                   const qahw_effect_uuid_t *uuid,
                                   qahw_effect_descriptor_t *effect_desc) {
    int32_t rc = -EINVAL;
    audio_effect_library_t *desc = (audio_effect_library_t *)handle;

    if (desc != NULL) {
        rc = desc->get_descriptor((const effect_uuid_t *)uuid, (effect_descriptor_t *)effect_desc);
    }

    return rc;
}


int32_t qahw_effect_get_version() {
    return QAHW_EFFECT_API_VERSION_CURRENT;
}


int32_t qahw_effect_process(qahw_effect_handle_t self,
                            qahw_audio_buffer_t *in_buffer,
                            qahw_audio_buffer_t *out_buffer) {
    int32_t rc = -EINVAL;
    struct effect_interface_s *itfe;

    if (self) {
        itfe = *((struct effect_interface_s **)self);
        if (itfe) {
            rc = itfe->process((effect_handle_t)self,
                               (audio_buffer_t *)in_buffer,
                               (audio_buffer_t *)out_buffer);
        }
    }

    return rc;
}


int32_t qahw_effect_command(qahw_effect_handle_t self,
                            uint32_t cmd_code,
                            uint32_t cmd_size,
                            void *cmd_data,
                            uint32_t *reply_size,
                            void *reply_data) {
    int32_t rc = -EINVAL;
    struct effect_interface_s *itfe;

    if (self) {
        itfe = *((struct effect_interface_s **)self);
        if (itfe) {
            rc = itfe->command((effect_handle_t)self, cmd_code, cmd_size,
                               cmd_data, reply_size, reply_data);
        }
    }

    return rc;
}


int32_t qahw_effect_process_reverse(qahw_effect_handle_t self,
                                    qahw_audio_buffer_t *in_buffer,
                                    qahw_audio_buffer_t *out_buffer) {
    int32_t rc = -EINVAL;
    struct effect_interface_s *itfe;

    if (self) {
        itfe = *((struct effect_interface_s **)self);
        if (itfe) {
            rc = itfe->process_reverse((effect_handle_t)self,
                                       (audio_buffer_t *)in_buffer,
                                       (audio_buffer_t *)out_buffer);
        }
    }

    return rc;
}

