/*
 * 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 "offload_effect_bundle"
//#define LOG_NDEBUG 0

#include <stdlib.h>
#include <cutils/list.h>
#include <cutils/log.h>
#include <system/thread_defs.h>
#include <tinyalsa/asoundlib.h>
#include <hardware/audio_effect.h>

#include "bundle.h"
#include "equalizer.h"
#include "bass_boost.h"
#include "virtualizer.h"
#include "reverb.h"

enum {
    EFFECT_STATE_UNINITIALIZED,
    EFFECT_STATE_INITIALIZED,
    EFFECT_STATE_ACTIVE,
};

const effect_descriptor_t *descriptors[] = {
        &equalizer_descriptor,
        &bassboost_descriptor,
        &virtualizer_descriptor,
        &aux_env_reverb_descriptor,
        &ins_env_reverb_descriptor,
        &aux_preset_reverb_descriptor,
        &ins_preset_reverb_descriptor,
        NULL,
};

pthread_once_t once = PTHREAD_ONCE_INIT;
int init_status;
/*
 * list of created effects.
 * Updated by offload_effects_bundle_hal_start_output()
 * and offload_effects_bundle_hal_stop_output()
 */
struct listnode created_effects_list;
/*
 * list of active output streams.
 * Updated by offload_effects_bundle_hal_start_output()
 * and offload_effects_bundle_hal_stop_output()
 */
struct listnode active_outputs_list;
/*
 * lock must be held when modifying or accessing
 * created_effects_list or active_outputs_list
 */
pthread_mutex_t lock;


/*
 *  Local functions
 */
static void init_once() {
    list_init(&created_effects_list);
    list_init(&active_outputs_list);

    pthread_mutex_init(&lock, NULL);

    init_status = 0;
}

int lib_init()
{
    pthread_once(&once, init_once);
    return init_status;
}

bool effect_exists(effect_context_t *context)
{
    struct listnode *node;

    list_for_each(node, &created_effects_list) {
        effect_context_t *fx_ctxt = node_to_item(node,
                                                 effect_context_t,
                                                 effects_list_node);
        if (fx_ctxt == context) {
            return true;
        }
    }
    return false;
}

output_context_t *get_output(audio_io_handle_t output)
{
    struct listnode *node;

    list_for_each(node, &active_outputs_list) {
        output_context_t *out_ctxt = node_to_item(node,
                                                  output_context_t,
                                                  outputs_list_node);
        if (out_ctxt->handle == output)
            return out_ctxt;
    }
    return NULL;
}

void add_effect_to_output(output_context_t * output, effect_context_t *context)
{
    struct listnode *fx_node;

    list_for_each(fx_node, &output->effects_list) {
        effect_context_t *fx_ctxt = node_to_item(fx_node,
                                                 effect_context_t,
                                                 output_node);
        if (fx_ctxt == context)
            return;
    }
    list_add_tail(&output->effects_list, &context->output_node);
    if (context->ops.start)
        context->ops.start(context, output);

}

void remove_effect_from_output(output_context_t * output,
                               effect_context_t *context)
{
    struct listnode *fx_node;

    list_for_each(fx_node, &output->effects_list) {
        effect_context_t *fx_ctxt = node_to_item(fx_node,
                                                 effect_context_t,
                                                 output_node);
        if (fx_ctxt == context) {
            if (context->ops.stop)
                context->ops.stop(context, output);
            list_remove(&context->output_node);
            return;
        }
    }
}

bool effects_enabled()
{
    struct listnode *out_node;

    list_for_each(out_node, &active_outputs_list) {
        struct listnode *fx_node;
        output_context_t *out_ctxt = node_to_item(out_node,
                                                  output_context_t,
                                                  outputs_list_node);

        list_for_each(fx_node, &out_ctxt->effects_list) {
            effect_context_t *fx_ctxt = node_to_item(fx_node,
                                                     effect_context_t,
                                                     output_node);
            if ((fx_ctxt->state == EFFECT_STATE_ACTIVE) &&
                (fx_ctxt->ops.process != NULL))
                return true;
        }
    }
    return false;
}


/*
 * Interface from audio HAL
 */
__attribute__ ((visibility ("default")))
int offload_effects_bundle_hal_start_output(audio_io_handle_t output, int pcm_id)
{
    int ret = 0;
    struct listnode *node;
    char mixer_string[128];
    output_context_t * out_ctxt = NULL;

    ALOGV("%s output %d pcm_id %d", __func__, output, pcm_id);

    if (lib_init() != 0)
        return init_status;

    pthread_mutex_lock(&lock);
    if (get_output(output) != NULL) {
        ALOGW("%s output already started", __func__);
        ret = -ENOSYS;
        goto exit;
    }

    out_ctxt = (output_context_t *)
                                 malloc(sizeof(output_context_t));
    out_ctxt->handle = output;
    out_ctxt->pcm_device_id = pcm_id;

    /* populate the mixer control to send offload parameters */
    snprintf(mixer_string, sizeof(mixer_string),
             "%s %d", "Audio Effects Config", out_ctxt->pcm_device_id);
    out_ctxt->mixer = mixer_open(MIXER_CARD);
    if (!out_ctxt->mixer) {
        ALOGE("Failed to open mixer");
        out_ctxt->ctl = NULL;
        ret = -EINVAL;
        free(out_ctxt);
        goto exit;
    } else {
        out_ctxt->ctl = mixer_get_ctl_by_name(out_ctxt->mixer, mixer_string);
        if (!out_ctxt->ctl) {
            ALOGE("mixer_get_ctl_by_name failed");
            mixer_close(out_ctxt->mixer);
            out_ctxt->mixer = NULL;
            ret = -EINVAL;
            free(out_ctxt);
            goto exit;
        }
    }

    list_init(&out_ctxt->effects_list);

    list_for_each(node, &created_effects_list) {
        effect_context_t *fx_ctxt = node_to_item(node,
                                                 effect_context_t,
                                                 effects_list_node);
        if (fx_ctxt->out_handle == output) {
            if (fx_ctxt->ops.start)
                fx_ctxt->ops.start(fx_ctxt, out_ctxt);
            list_add_tail(&out_ctxt->effects_list, &fx_ctxt->output_node);
        }
    }
    list_add_tail(&active_outputs_list, &out_ctxt->outputs_list_node);
exit:
    pthread_mutex_unlock(&lock);
    return ret;
}

__attribute__ ((visibility ("default")))
int offload_effects_bundle_hal_stop_output(audio_io_handle_t output, int pcm_id)
{
    int ret;
    struct listnode *node;
    struct listnode *fx_node;
    output_context_t *out_ctxt;

    ALOGV("%s output %d pcm_id %d", __func__, output, pcm_id);

    if (lib_init() != 0)
        return init_status;

    pthread_mutex_lock(&lock);

    out_ctxt = get_output(output);
    if (out_ctxt == NULL) {
        ALOGW("%s output not started", __func__);
        ret = -ENOSYS;
        goto exit;
    }

    if (out_ctxt->mixer)
        mixer_close(out_ctxt->mixer);

    list_for_each(fx_node, &out_ctxt->effects_list) {
        effect_context_t *fx_ctxt = node_to_item(fx_node,
                                                 effect_context_t,
                                                 output_node);
        if (fx_ctxt->ops.stop)
            fx_ctxt->ops.stop(fx_ctxt, out_ctxt);
    }

    list_remove(&out_ctxt->outputs_list_node);

    free(out_ctxt);

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


/*
 * Effect operations
 */
int set_config(effect_context_t *context, effect_config_t *config)
{
    context->config = *config;

    if (context->ops.reset)
        context->ops.reset(context);

    return 0;
}

void get_config(effect_context_t *context, effect_config_t *config)
{
    *config = context->config;
}


/*
 * Effect Library Interface Implementation
 */
int effect_lib_create(const effect_uuid_t *uuid,
                         int32_t sessionId,
                         int32_t ioId,
                         effect_handle_t *pHandle) {
    int ret;
    int i;

    ALOGV("%s: sessionId: %d, ioId: %d", __func__, sessionId, ioId);
    if (lib_init() != 0)
        return init_status;

    if (pHandle == NULL || uuid == NULL)
        return -EINVAL;

    for (i = 0; descriptors[i] != NULL; i++) {
        if (memcmp(uuid, &descriptors[i]->uuid, sizeof(effect_uuid_t)) == 0)
            break;
    }

    if (descriptors[i] == NULL)
        return -EINVAL;

    effect_context_t *context;
    if (memcmp(uuid, &equalizer_descriptor.uuid,
        sizeof(effect_uuid_t)) == 0) {
        equalizer_context_t *eq_ctxt = (equalizer_context_t *)
                                       calloc(1, sizeof(equalizer_context_t));
        context = (effect_context_t *)eq_ctxt;
        context->ops.init = equalizer_init;
        context->ops.reset = equalizer_reset;
        context->ops.set_parameter = equalizer_set_parameter;
        context->ops.get_parameter = equalizer_get_parameter;
        context->ops.set_device = equalizer_set_device;
        context->ops.enable = equalizer_enable;
        context->ops.disable = equalizer_disable;
        context->ops.start = equalizer_start;
        context->ops.stop = equalizer_stop;

        context->desc = &equalizer_descriptor;
        eq_ctxt->ctl = NULL;
    } else if (memcmp(uuid, &bassboost_descriptor.uuid,
               sizeof(effect_uuid_t)) == 0) {
        bassboost_context_t *bass_ctxt = (bassboost_context_t *)
                                         calloc(1, sizeof(bassboost_context_t));
        context = (effect_context_t *)bass_ctxt;
        context->ops.init = bassboost_init;
        context->ops.reset = bassboost_reset;
        context->ops.set_parameter = bassboost_set_parameter;
        context->ops.get_parameter = bassboost_get_parameter;
        context->ops.set_device = bassboost_set_device;
        context->ops.enable = bassboost_enable;
        context->ops.disable = bassboost_disable;
        context->ops.start = bassboost_start;
        context->ops.stop = bassboost_stop;

        context->desc = &bassboost_descriptor;
        bass_ctxt->ctl = NULL;
    } else if (memcmp(uuid, &virtualizer_descriptor.uuid,
               sizeof(effect_uuid_t)) == 0) {
        virtualizer_context_t *virt_ctxt = (virtualizer_context_t *)
                                           calloc(1, sizeof(virtualizer_context_t));
        context = (effect_context_t *)virt_ctxt;
        context->ops.init = virtualizer_init;
        context->ops.reset = virtualizer_reset;
        context->ops.set_parameter = virtualizer_set_parameter;
        context->ops.get_parameter = virtualizer_get_parameter;
        context->ops.set_device = virtualizer_set_device;
        context->ops.enable = virtualizer_enable;
        context->ops.disable = virtualizer_disable;
        context->ops.start = virtualizer_start;
        context->ops.stop = virtualizer_stop;

        context->desc = &virtualizer_descriptor;
        virt_ctxt->ctl = NULL;
    } else if ((memcmp(uuid, &aux_env_reverb_descriptor.uuid,
                sizeof(effect_uuid_t)) == 0) ||
               (memcmp(uuid, &ins_env_reverb_descriptor.uuid,
                sizeof(effect_uuid_t)) == 0) ||
               (memcmp(uuid, &aux_preset_reverb_descriptor.uuid,
                sizeof(effect_uuid_t)) == 0) ||
               (memcmp(uuid, &ins_preset_reverb_descriptor.uuid,
                sizeof(effect_uuid_t)) == 0)) {
        reverb_context_t *reverb_ctxt = (reverb_context_t *)
                                        calloc(1, sizeof(reverb_context_t));
        context = (effect_context_t *)reverb_ctxt;
        context->ops.init = reverb_init;
        context->ops.reset = reverb_reset;
        context->ops.set_parameter = reverb_set_parameter;
        context->ops.get_parameter = reverb_get_parameter;
        context->ops.set_device = reverb_set_device;
        context->ops.enable = reverb_enable;
        context->ops.disable = reverb_disable;
        context->ops.start = reverb_start;
        context->ops.stop = reverb_stop;

        if (memcmp(uuid, &aux_env_reverb_descriptor.uuid,
                   sizeof(effect_uuid_t)) == 0) {
            context->desc = &aux_env_reverb_descriptor;
            reverb_auxiliary_init(reverb_ctxt);
        } else if (memcmp(uuid, &ins_env_reverb_descriptor.uuid,
                   sizeof(effect_uuid_t)) == 0) {
            context->desc = &ins_env_reverb_descriptor;
            reverb_insert_init(reverb_ctxt);
        } else if (memcmp(uuid, &aux_preset_reverb_descriptor.uuid,
                   sizeof(effect_uuid_t)) == 0) {
            context->desc = &aux_preset_reverb_descriptor;
            reverb_auxiliary_init(reverb_ctxt);
        } else if (memcmp(uuid, &ins_preset_reverb_descriptor.uuid,
                   sizeof(effect_uuid_t)) == 0) {
            context->desc = &ins_preset_reverb_descriptor;
            reverb_preset_init(reverb_ctxt);
        }
        reverb_ctxt->ctl = NULL;
    } else {
        return -EINVAL;
    }

    context->itfe = &effect_interface;
    context->state = EFFECT_STATE_UNINITIALIZED;
    context->out_handle = (audio_io_handle_t)ioId;

    ret = context->ops.init(context);
    if (ret < 0) {
        ALOGW("%s init failed", __func__);
        free(context);
        return ret;
    }

    context->state = EFFECT_STATE_INITIALIZED;

    pthread_mutex_lock(&lock);
    list_add_tail(&created_effects_list, &context->effects_list_node);
    output_context_t *out_ctxt = get_output(ioId);
    if (out_ctxt != NULL)
        add_effect_to_output(out_ctxt, context);
    pthread_mutex_unlock(&lock);

    *pHandle = (effect_handle_t)context;

    ALOGV("%s created context %p", __func__, context);

    return 0;

}

int effect_lib_release(effect_handle_t handle)
{
    effect_context_t *context = (effect_context_t *)handle;
    int status;

    if (lib_init() != 0)
        return init_status;

    ALOGV("%s context %p", __func__, handle);
    pthread_mutex_lock(&lock);
    status = -EINVAL;
    if (effect_exists(context)) {
        output_context_t *out_ctxt = get_output(context->out_handle);
        if (out_ctxt != NULL)
            remove_effect_from_output(out_ctxt, context);
        list_remove(&context->effects_list_node);
        if (context->ops.release)
            context->ops.release(context);
        free(context);
        status = 0;
    }
    pthread_mutex_unlock(&lock);

    return status;
}

int effect_lib_get_descriptor(const effect_uuid_t *uuid,
                              effect_descriptor_t *descriptor)
{
    int i;

    if (lib_init() != 0)
        return init_status;

    if (descriptor == NULL || uuid == NULL) {
        ALOGV("%s called with NULL pointer", __func__);
        return -EINVAL;
    }

    for (i = 0; descriptors[i] != NULL; i++) {
        if (memcmp(uuid, &descriptors[i]->uuid, sizeof(effect_uuid_t)) == 0) {
            *descriptor = *descriptors[i];
            return 0;
        }
    }

    return  -EINVAL;
}


/*
 * Effect Control Interface Implementation
 */

/* Stub function for effect interface: never called for offloaded effects */
int effect_process(effect_handle_t self,
                       audio_buffer_t *inBuffer __unused,
                       audio_buffer_t *outBuffer __unused)
{
    effect_context_t * context = (effect_context_t *)self;
    int status = 0;

    ALOGW("%s Called ?????", __func__);

    pthread_mutex_lock(&lock);
    if (!effect_exists(context)) {
        status = -ENOSYS;
        goto exit;
    }

    if (context->state != EFFECT_STATE_ACTIVE) {
        status = -ENODATA;
        goto exit;
    }

exit:
    pthread_mutex_unlock(&lock);
    return status;
}

int effect_command(effect_handle_t self, uint32_t cmdCode, uint32_t cmdSize,
                   void *pCmdData, uint32_t *replySize, void *pReplyData)
{

    effect_context_t * context = (effect_context_t *)self;
    int retsize;
    int status = 0;

    pthread_mutex_lock(&lock);

    if (!effect_exists(context)) {
        status = -ENOSYS;
        goto exit;
    }

    if (context == NULL || context->state == EFFECT_STATE_UNINITIALIZED) {
        status = -ENOSYS;
        goto exit;
    }

    switch (cmdCode) {
    case EFFECT_CMD_INIT:
        if (pReplyData == NULL || *replySize != sizeof(int)) {
            status = -EINVAL;
            goto exit;
        }
        if (context->ops.init)
            *(int *) pReplyData = context->ops.init(context);
        else
            *(int *) pReplyData = 0;
        break;
    case EFFECT_CMD_SET_CONFIG:
        if (pCmdData == NULL || cmdSize != sizeof(effect_config_t)
                || pReplyData == NULL || *replySize != sizeof(int)) {
            status = -EINVAL;
            goto exit;
        }
        *(int *) pReplyData = set_config(context, (effect_config_t *) pCmdData);
        break;
    case EFFECT_CMD_GET_CONFIG:
        if (pReplyData == NULL ||
            *replySize != sizeof(effect_config_t)) {
            status = -EINVAL;
            goto exit;
        }
        if (!context->offload_enabled) {
            status = -EINVAL;
            goto exit;
        }

        get_config(context, (effect_config_t *)pReplyData);
        break;
    case EFFECT_CMD_RESET:
        if (context->ops.reset)
            context->ops.reset(context);
        break;
    case EFFECT_CMD_ENABLE:
        if (pReplyData == NULL || *replySize != sizeof(int)) {
            status = -EINVAL;
            goto exit;
        }
        if (context->state != EFFECT_STATE_INITIALIZED) {
            status = -ENOSYS;
            goto exit;
        }
        context->state = EFFECT_STATE_ACTIVE;
        if (context->ops.enable)
            context->ops.enable(context);
        ALOGV("%s EFFECT_CMD_ENABLE", __func__);
        *(int *)pReplyData = 0;
        break;
    case EFFECT_CMD_DISABLE:
        if (pReplyData == NULL || *replySize != sizeof(int)) {
            status = -EINVAL;
            goto exit;
        }
        if (context->state != EFFECT_STATE_ACTIVE) {
            status = -ENOSYS;
            goto exit;
        }
        context->state = EFFECT_STATE_INITIALIZED;
        if (context->ops.disable)
            context->ops.disable(context);
        ALOGV("%s EFFECT_CMD_DISABLE", __func__);
        *(int *)pReplyData = 0;
        break;
    case EFFECT_CMD_GET_PARAM: {
        if (pCmdData == NULL ||
            cmdSize < (int)(sizeof(effect_param_t) + sizeof(uint32_t)) ||
            pReplyData == NULL ||
            *replySize < (int)(sizeof(effect_param_t) + sizeof(uint32_t) + sizeof(uint16_t)) ||
            // constrain memcpy below
            ((effect_param_t *)pCmdData)->psize > *replySize - sizeof(effect_param_t)) {
            status = -EINVAL;
            ALOGV("EFFECT_CMD_GET_PARAM invalid command cmdSize %d *replySize %d",
                  cmdSize, *replySize);
            goto exit;
        }
        if (!context->offload_enabled) {
            status = -EINVAL;
            goto exit;
        }
        effect_param_t *q = (effect_param_t *)pCmdData;
        memcpy(pReplyData, pCmdData, sizeof(effect_param_t) + q->psize);
        effect_param_t *p = (effect_param_t *)pReplyData;
        if (context->ops.get_parameter)
            context->ops.get_parameter(context, p, replySize);
        } break;
    case EFFECT_CMD_SET_PARAM: {
        if (pCmdData == NULL ||
            cmdSize < (int)(sizeof(effect_param_t) + sizeof(uint32_t) +
                            sizeof(uint16_t)) ||
            pReplyData == NULL || *replySize != sizeof(int32_t)) {
            status = -EINVAL;
            ALOGV("EFFECT_CMD_SET_PARAM invalid command cmdSize %d *replySize %d",
                  cmdSize, *replySize);
            goto exit;
        }
        *(int32_t *)pReplyData = 0;
        effect_param_t *p = (effect_param_t *)pCmdData;
        if (context->ops.set_parameter)
            *(int32_t *)pReplyData = context->ops.set_parameter(context, p,
                                                                *replySize);

        } break;
    case EFFECT_CMD_SET_DEVICE: {
        uint32_t device;
        ALOGV("\t EFFECT_CMD_SET_DEVICE start");
        if (pCmdData == NULL || cmdSize < sizeof(uint32_t)) {
            status = -EINVAL;
            ALOGV("EFFECT_CMD_SET_DEVICE invalid command cmdSize %d", cmdSize);
            goto exit;
        }
        device = *(uint32_t *)pCmdData;
        if (context->ops.set_device)
            context->ops.set_device(context, device);
        } break;
    case EFFECT_CMD_SET_VOLUME:
    case EFFECT_CMD_SET_AUDIO_MODE:
        break;

    case EFFECT_CMD_OFFLOAD: {
        output_context_t *out_ctxt;

        if (cmdSize != sizeof(effect_offload_param_t) || pCmdData == NULL
                || pReplyData == NULL || *replySize != sizeof(int)) {
            ALOGV("%s EFFECT_CMD_OFFLOAD bad format", __func__);
            status = -EINVAL;
            break;
        }

        effect_offload_param_t* offload_param = (effect_offload_param_t*)pCmdData;

        ALOGV("%s EFFECT_CMD_OFFLOAD offload %d output %d", __func__,
              offload_param->isOffload, offload_param->ioHandle);

        *(int *)pReplyData = 0;

        context->offload_enabled = offload_param->isOffload;
        if (context->out_handle == offload_param->ioHandle)
            break;

        out_ctxt = get_output(context->out_handle);
        if (out_ctxt != NULL)
            remove_effect_from_output(out_ctxt, context);

        context->out_handle = offload_param->ioHandle;
        out_ctxt = get_output(context->out_handle);
        if (out_ctxt != NULL)
            add_effect_to_output(out_ctxt, context);

        } break;


    default:
        if (cmdCode >= EFFECT_CMD_FIRST_PROPRIETARY && context->ops.command)
            status = context->ops.command(context, cmdCode, cmdSize,
                                          pCmdData, replySize, pReplyData);
        else {
            ALOGW("%s invalid command %d", __func__, cmdCode);
            status = -EINVAL;
        }
        break;
    }

exit:
    pthread_mutex_unlock(&lock);

    return status;
}

/* Effect Control Interface Implementation: get_descriptor */
int effect_get_descriptor(effect_handle_t   self,
                          effect_descriptor_t *descriptor)
{
    effect_context_t *context = (effect_context_t *)self;

    if (!effect_exists(context) || (descriptor == NULL))
        return -EINVAL;

    *descriptor = *context->desc;

    return 0;
}

bool effect_is_active(effect_context_t * ctxt) {
    return ctxt->state == EFFECT_STATE_ACTIVE;
}

/* effect_handle_t interface implementation for offload effects */
const struct effect_interface_s effect_interface = {
    effect_process,
    effect_command,
    effect_get_descriptor,
    NULL,
};

__attribute__ ((visibility ("default")))
audio_effect_library_t AUDIO_EFFECT_LIBRARY_INFO_SYM = {
    tag : AUDIO_EFFECT_LIBRARY_TAG,
    version : EFFECT_LIBRARY_API_VERSION,
    name : "Offload Effects Bundle Library",
    implementor : "The Android Open Source Project",
    create_effect : effect_lib_create,
    release_effect : effect_lib_release,
    get_descriptor : effect_lib_get_descriptor,
};