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

#include "EffectsFactory.h"
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>


static list_elem_t *gEffectList; // list of effect_entry_t: all currently created effects
static list_elem_t *gLibraryList; // list of lib_entry_t: all currently loaded libraries
static pthread_mutex_t gLibLock = PTHREAD_MUTEX_INITIALIZER; // controls access to gLibraryList
static uint32_t gNumEffects;         // total number number of effects
static list_elem_t *gCurLib;    // current library in enumeration process
static list_elem_t *gCurEffect; // current effect in enumeration process
static uint32_t gCurEffectIdx;       // current effect index in enumeration process

const char * const gEffectLibPath = "/system/lib/soundfx"; // path to built-in effect libraries
static int gInitDone; // true is global initialization has been preformed
static int gNextLibId; // used by loadLibrary() to allocate unique library handles
static int gCanQueryEffect; // indicates that call to EffectQueryEffect() is valid, i.e. that the list of effects
                          // was not modified since last call to EffectQueryNumberEffects()

/////////////////////////////////////////////////
//      Local functions prototypes
/////////////////////////////////////////////////

static int init();
static int loadLibrary(const char *libPath, int *handle);
static int unloadLibrary(int handle);
static void resetEffectEnumeration();
static uint32_t updateNumEffects();
static int findEffect(effect_uuid_t *uuid, lib_entry_t **lib, effect_descriptor_t **desc);
static void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len);

/////////////////////////////////////////////////
//      Effect Control Interface functions
/////////////////////////////////////////////////

int Effect_Process(effect_interface_t self, audio_buffer_t *inBuffer, audio_buffer_t *outBuffer)
{
    int ret = init();
    if (ret < 0) {
        return ret;
    }
    effect_entry_t *fx = (effect_entry_t *)self;
    pthread_mutex_lock(&gLibLock);
    if (fx->lib == NULL) {
        pthread_mutex_unlock(&gLibLock);
        return -EPIPE;
    }
    pthread_mutex_lock(&fx->lib->lock);
    pthread_mutex_unlock(&gLibLock);

    ret = (*fx->subItfe)->process(fx->subItfe, inBuffer, outBuffer);
    pthread_mutex_unlock(&fx->lib->lock);
    return ret;
}

int Effect_Command(effect_interface_t self,
                   uint32_t cmdCode,
                   uint32_t cmdSize,
                   void *pCmdData,
                   uint32_t *replySize,
                   void *pReplyData)
{
    int ret = init();
    if (ret < 0) {
        return ret;
    }
    effect_entry_t *fx = (effect_entry_t *)self;
    pthread_mutex_lock(&gLibLock);
    if (fx->lib == NULL) {
        pthread_mutex_unlock(&gLibLock);
        return -EPIPE;
    }
    pthread_mutex_lock(&fx->lib->lock);
    pthread_mutex_unlock(&gLibLock);

    ret = (*fx->subItfe)->command(fx->subItfe, cmdCode, cmdSize, pCmdData, replySize, pReplyData);
    pthread_mutex_unlock(&fx->lib->lock);
    return ret;
}

const struct effect_interface_s gInterface = {
        Effect_Process,
        Effect_Command
};

/////////////////////////////////////////////////
//      Effect Factory Interface functions
/////////////////////////////////////////////////

int EffectQueryNumberEffects(uint32_t *pNumEffects)
{
    int ret = init();
    if (ret < 0) {
        return ret;
    }
    if (pNumEffects == NULL) {
        return -EINVAL;
    }

    pthread_mutex_lock(&gLibLock);
    *pNumEffects = gNumEffects;
    gCanQueryEffect = 1;
    pthread_mutex_unlock(&gLibLock);
    LOGV("EffectQueryNumberEffects(): %d", *pNumEffects);
    return ret;
}

int EffectQueryEffect(uint32_t index, effect_descriptor_t *pDescriptor)
{
    int ret = init();
    if (ret < 0) {
        return ret;
    }
    if (pDescriptor == NULL ||
        index >= gNumEffects) {
        return -EINVAL;
    }
    if (gCanQueryEffect == 0) {
        return -ENOSYS;
    }

    pthread_mutex_lock(&gLibLock);
    ret = -ENOENT;
    if (index < gCurEffectIdx) {
        resetEffectEnumeration();
    }
    while (gCurLib) {
        if (gCurEffect) {
            if (index == gCurEffectIdx) {
                memcpy(pDescriptor, gCurEffect->object, sizeof(effect_descriptor_t));
                ret = 0;
                break;
            } else {
                gCurEffect = gCurEffect->next;
                gCurEffectIdx++;
            }
        } else {
            gCurLib = gCurLib->next;
            gCurEffect = ((lib_entry_t *)gCurLib->object)->effects;
        }
    }

#if (LOG_NDEBUG == 0)
    char str[256];
    dumpEffectDescriptor(pDescriptor, str, 256);
    LOGV("EffectQueryEffect() desc:%s", str);
#endif
    pthread_mutex_unlock(&gLibLock);
    return ret;
}

int EffectGetDescriptor(effect_uuid_t *uuid, effect_descriptor_t *pDescriptor)
{
    lib_entry_t *l = NULL;
    effect_descriptor_t *d = NULL;

    int ret = init();
    if (ret < 0) {
        return ret;
    }
    if (pDescriptor == NULL || uuid == NULL) {
        return -EINVAL;
    }
    pthread_mutex_lock(&gLibLock);
    ret = findEffect(uuid, &l, &d);
    if (ret == 0) {
        memcpy(pDescriptor, d, sizeof(effect_descriptor_t));
    }
    pthread_mutex_unlock(&gLibLock);
    return ret;
}

int EffectCreate(effect_uuid_t *uuid, int32_t sessionId, int32_t ioId, effect_interface_t *pInterface)
{
    list_elem_t *e = gLibraryList;
    lib_entry_t *l = NULL;
    effect_descriptor_t *d = NULL;
    effect_interface_t itfe;
    effect_entry_t *fx;
    int found = 0;
    int ret;

    if (uuid == NULL || pInterface == NULL) {
        return -EINVAL;
    }

    LOGV("EffectCreate() UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
            uuid->timeLow, uuid->timeMid, uuid->timeHiAndVersion,
            uuid->clockSeq, uuid->node[0], uuid->node[1],uuid->node[2],
            uuid->node[3],uuid->node[4],uuid->node[5]);

    ret = init();

    if (ret < 0) {
        LOGW("EffectCreate() init error: %d", ret);
        return ret;
    }

    pthread_mutex_lock(&gLibLock);

    ret = findEffect(uuid, &l, &d);
    if (ret < 0){
        goto exit;
    }

    // create effect in library
    ret = l->createFx(uuid, sessionId, ioId, &itfe);
    if (ret != 0) {
        LOGW("EffectCreate() library %s: could not create fx %s, error %d", l->path, d->name, ret);
        goto exit;
    }

    // add entry to effect list
    fx = (effect_entry_t *)malloc(sizeof(effect_entry_t));
    fx->subItfe = itfe;
    fx->itfe = (struct effect_interface_s *)&gInterface;
    fx->lib = l;

    e = (list_elem_t *)malloc(sizeof(list_elem_t));
    e->object = fx;
    e->next = gEffectList;
    gEffectList = e;

    *pInterface = (effect_interface_t)fx;

    LOGV("EffectCreate() created entry %p with sub itfe %p in library %s", *pInterface, itfe, l->path);

exit:
    pthread_mutex_unlock(&gLibLock);
    return ret;
}

int EffectRelease(effect_interface_t interface)
{
    effect_entry_t *fx;
    list_elem_t *e1;
    list_elem_t *e2;

    int ret = init();
    if (ret < 0) {
        return ret;
    }

    // remove effect from effect list
    pthread_mutex_lock(&gLibLock);
    e1 = gEffectList;
    e2 = NULL;
    while (e1) {
        if (e1->object == interface) {
            if (e2) {
                e2->next = e1->next;
            } else {
                gEffectList = e1->next;
            }
            fx = (effect_entry_t *)e1->object;
            free(e1);
            break;
        }
        e2 = e1;
        e1 = e1->next;
    }
    if (e1 == NULL) {
        ret = -ENOENT;
        goto exit;
    }

    // release effect in library
    if (fx->lib == NULL) {
        LOGW("EffectRelease() fx %p library already unloaded", interface);
    } else {
        pthread_mutex_lock(&fx->lib->lock);
        fx->lib->releaseFx(fx->subItfe);
        pthread_mutex_unlock(&fx->lib->lock);
    }
    free(fx);

exit:
    pthread_mutex_unlock(&gLibLock);
    return ret;
}

int EffectLoadLibrary(const char *libPath, int *handle)
{
    int ret = init();
    if (ret < 0) {
        return ret;
    }
    if (libPath == NULL) {
        return -EINVAL;
    }

    ret = loadLibrary(libPath, handle);
    updateNumEffects();
    return ret;
}

int EffectUnloadLibrary(int handle)
{
    int ret = init();
    if (ret < 0) {
        return ret;
    }

    ret = unloadLibrary(handle);
    updateNumEffects();
    return ret;
}

int EffectIsNullUuid(effect_uuid_t *uuid)
{
    if (memcmp(uuid, EFFECT_UUID_NULL, sizeof(effect_uuid_t))) {
        return 0;
    }
    return 1;
}

/////////////////////////////////////////////////
//      Local functions
/////////////////////////////////////////////////

int init() {
    struct dirent *ent;
    DIR *dir = NULL;
    char libpath[PATH_MAX];
    int hdl;

    if (gInitDone) {
        return 0;
    }

    pthread_mutex_init(&gLibLock, NULL);

    // load built-in libraries
    dir = opendir(gEffectLibPath);
    if (dir == NULL) {
        return -ENODEV;
    }
    while ((ent = readdir(dir)) != NULL) {
        LOGV("init() reading file %s", ent->d_name);
        if ((strlen(ent->d_name) < 3) ||
            strncmp(ent->d_name, "lib", 3) != 0 ||
            strncmp(ent->d_name + strlen(ent->d_name) - 3, ".so", 3) != 0) {
            continue;
        }
        strcpy(libpath, gEffectLibPath);
        strcat(libpath, "/");
        strcat(libpath, ent->d_name);
        if (loadLibrary(libpath, &hdl) < 0) {
            LOGW("init() failed to load library %s",libpath);
        }
    }
    closedir(dir);
    updateNumEffects();
    gInitDone = 1;
    LOGV("init() done");
    return 0;
}


int loadLibrary(const char *libPath, int *handle)
{
    void *hdl;
    effect_QueryNumberEffects_t queryNumFx;
    effect_QueryEffect_t queryFx;
    effect_CreateEffect_t createFx;
    effect_ReleaseEffect_t releaseFx;
    uint32_t numFx;
    uint32_t fx;
    int ret;
    list_elem_t *e, *descHead = NULL;
    lib_entry_t *l;

    if (handle == NULL) {
        return -EINVAL;
    }

    *handle = 0;

    hdl = dlopen(libPath, RTLD_NOW);
    if (hdl == 0) {
        LOGW("could open lib %s", libPath);
        return -ENODEV;
    }

    // Check functions availability
    queryNumFx = (effect_QueryNumberEffects_t)dlsym(hdl, "EffectQueryNumberEffects");
    if (queryNumFx == NULL) {
        LOGW("could not get EffectQueryNumberEffects from lib %s", libPath);
        ret = -ENODEV;
        goto error;
    }
    queryFx = (effect_QueryEffect_t)dlsym(hdl, "EffectQueryEffect");
    if (queryFx == NULL) {
        LOGW("could not get EffectQueryEffect from lib %s", libPath);
        ret = -ENODEV;
        goto error;
    }
    createFx = (effect_CreateEffect_t)dlsym(hdl, "EffectCreate");
    if (createFx == NULL) {
        LOGW("could not get EffectCreate from lib %s", libPath);
        ret = -ENODEV;
        goto error;
    }
    releaseFx = (effect_ReleaseEffect_t)dlsym(hdl, "EffectRelease");
    if (releaseFx == NULL) {
        LOGW("could not get EffectRelease from lib %s", libPath);
        ret = -ENODEV;
        goto error;
    }

    // load effect descriptors
    ret = queryNumFx(&numFx);
    if (ret) {
        goto error;
    }

    for (fx = 0; fx < numFx; fx++) {
        effect_descriptor_t *d = malloc(sizeof(effect_descriptor_t));
        if (d == NULL) {
            ret = -ENOMEM;
            goto error;
        }
        ret = queryFx(fx, d);
        if (ret == 0) {
#if (LOG_NDEBUG==0)
            char s[256];
            dumpEffectDescriptor(d, s, 256);
            LOGV("loadLibrary() read descriptor %p:%s",d, s);
#endif
            if (d->apiVersion != EFFECT_API_VERSION) {
                LOGW("Bad API version %04x on lib %s", d->apiVersion, libPath);
                free(d);
                continue;
            }
            e = malloc(sizeof(list_elem_t));
            if (e == NULL) {
                free(d);
                ret = -ENOMEM;
                goto error;
            }
            e->object = d;
            e->next = descHead;
            descHead = e;
        } else {
            LOGW("Error querying effect # %d on lib %s", fx, libPath);
        }
    }

    pthread_mutex_lock(&gLibLock);

    // add entry for library in gLibraryList
    l = malloc(sizeof(lib_entry_t));
    l->id = ++gNextLibId;
    l->handle = hdl;
    strncpy(l->path, libPath, PATH_MAX);
    l->createFx = createFx;
    l->releaseFx = releaseFx;
    l->effects = descHead;
    pthread_mutex_init(&l->lock, NULL);

    e = malloc(sizeof(list_elem_t));
    e->next = gLibraryList;
    e->object = l;
    gLibraryList = e;
    pthread_mutex_unlock(&gLibLock);
    LOGV("loadLibrary() linked library %p", l);

    *handle = l->id;

    return 0;

error:
    LOGW("loadLibrary() error: %d on lib: %s", ret, libPath);
    while (descHead) {
        free(descHead->object);
        e = descHead->next;
        free(descHead);
        descHead = e;;
    }
    dlclose(hdl);
    return ret;
}

int unloadLibrary(int handle)
{
    void *hdl;
    int ret;
    list_elem_t *el1, *el2;
    lib_entry_t *l;
    effect_entry_t *fx;

    pthread_mutex_lock(&gLibLock);
    el1 = gLibraryList;
    el2 = NULL;
    while (el1) {
        l = (lib_entry_t *)el1->object;
        if (handle == l->id) {
            if (el2) {
                el2->next = el1->next;
            } else {
                gLibraryList = el1->next;
            }
            free(el1);
            break;
        }
        el2 = el1;
        el1 = el1->next;
    }
    pthread_mutex_unlock(&gLibLock);
    if (el1 == NULL) {
        return -ENOENT;
    }

    // clear effect descriptor list
    el1 = l->effects;
    while (el1) {
        free(el1->object);
        el2 = el1->next;
        free(el1);
        el1 = el2;
    }

    // disable all effects from this library
    pthread_mutex_lock(&l->lock);

    el1 = gEffectList;
    while (el1) {
        fx = (effect_entry_t *)el1->object;
        if (fx->lib == l) {
            fx->lib = NULL;
        }
        el1 = el1->next;
    }
    pthread_mutex_unlock(&l->lock);

    dlclose(l->handle);
    free(l);
    return 0;
}

void resetEffectEnumeration()
{
    gCurLib = gLibraryList;
    gCurEffect = NULL;
    if (gCurLib) {
        gCurEffect = ((lib_entry_t *)gCurLib->object)->effects;
    }
    gCurEffectIdx = 0;
}

uint32_t updateNumEffects() {
    list_elem_t *e;
    uint32_t cnt = 0;

    resetEffectEnumeration();

    e = gLibraryList;
    while (e) {
        lib_entry_t *l = (lib_entry_t *)e->object;
        list_elem_t *efx = l->effects;
        while (efx) {
            cnt++;
            efx = efx->next;
        }
        e = e->next;
    }
    gNumEffects = cnt;
    gCanQueryEffect = 0;
    return cnt;
}

int findEffect(effect_uuid_t *uuid, lib_entry_t **lib, effect_descriptor_t **desc)
{
    list_elem_t *e = gLibraryList;
    lib_entry_t *l = NULL;
    effect_descriptor_t *d = NULL;
    int found = 0;
    int ret = 0;

    while (e && !found) {
        l = (lib_entry_t *)e->object;
        list_elem_t *efx = l->effects;
        while (efx) {
            d = (effect_descriptor_t *)efx->object;
            if (memcmp(&d->uuid, uuid, sizeof(effect_uuid_t)) == 0) {
                found = 1;
                break;
            }
            efx = efx->next;
        }
        e = e->next;
    }
    if (!found) {
        LOGV("findEffect() effect not found");
        ret = -ENOENT;
    } else {
        LOGV("findEffect() found effect: %s in lib %s", d->name, l->path);
        *lib = l;
        *desc = d;
    }

    return ret;
}

void dumpEffectDescriptor(effect_descriptor_t *desc, char *str, size_t len) {
    char s[256];

    snprintf(str, len, "\nEffect Descriptor %p:\n", desc);
    sprintf(s, "- UUID: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
            desc->uuid.timeLow, desc->uuid.timeMid, desc->uuid.timeHiAndVersion,
            desc->uuid.clockSeq, desc->uuid.node[0], desc->uuid.node[1],desc->uuid.node[2],
            desc->uuid.node[3],desc->uuid.node[4],desc->uuid.node[5]);
    strncat(str, s, len);
    sprintf(s, "- TYPE: %08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X\n",
                desc->type.timeLow, desc->type.timeMid, desc->type.timeHiAndVersion,
                desc->type.clockSeq, desc->type.node[0], desc->type.node[1],desc->type.node[2],
                desc->type.node[3],desc->type.node[4],desc->type.node[5]);
    strncat(str, s, len);
    sprintf(s, "- apiVersion: %04X\n- flags: %08X\n",
            desc->apiVersion, desc->flags);
    strncat(str, s, len);
    sprintf(s, "- name: %s\n", desc->name);
    strncat(str, s, len);
    sprintf(s, "- implementor: %s\n", desc->implementor);
    strncat(str, s, len);
}