/*
 * Copyright (c) 2016, 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 "BT_HAL_EXTN"
/*#define LOG_NDEBUG 0*/

#include <inttypes.h>
#include <cutils/log.h>
#include <audio_hw.h>
#include <audio_extn.h>
#include <platform_api.h>
#include <audio_utils/resampler.h>
#include <audio_utils/format.h>

#include <../../../../system/bt/audio_a2dp_hw/bthost_ipc.h>
#include <dlfcn.h>

#define DEFAULT_BUF_SIZE 6144

#define UNUSED(x) (void)(x)
#define ASSERTC(cond, msg, val) if (!(cond)) {ALOGE("### ASSERT : %s line %d %s (%d) ###", __FILE__, __LINE__, msg, val);}

static void *lib_handle = NULL;
bt_host_ipc_interface_t *ipc_if = NULL;

struct a2dp_stream_out {
    struct audio_stream_out stream;
    struct a2dp_stream_common common;
};


typedef struct bt_hal_module {
    struct audio_hw_device *a2dp_device;
    struct a2dp_stream_out *a2dp_output;
    struct audio_config config;
    audio_channel_mask_t in_channel_mask;
    int bit_width;
    struct resampler_itfe *resampler;
    void *data;
    void *reformatted_buf;
} bt_hal_module_t;

static int bt_create_resampler(uint32_t in_rate,
                        struct bt_hal_module *bt)
{
    int err = 0;

    ALOGV("%s", __FUNCTION__);

    if (bt->resampler == NULL) {
        err = create_resampler(in_rate,
                               bt->config.sample_rate,
                               popcount(bt->config.channel_mask),
                               RESAMPLER_QUALITY_DEFAULT,
                               NULL,
                               &bt->resampler);

        if (err) {
            ALOGE("%s: Failed to create resampler", __FUNCTION__);
            free(bt->resampler);
            bt->resampler = NULL;
            return -EINVAL;
        }

        ALOGV("%s: Created resampler[%p] with in_sample_rate[%d], out_sample_rate[%d]", __FUNCTION__, bt->resampler, in_rate, bt->config.sample_rate);
    }
    return 0;
}

static void bt_destroy_resampler(struct bt_hal_module *bt)
{
    ALOGV("%s", __FUNCTION__);

    if (bt->resampler != NULL) {
        release_resampler(bt->resampler);
        bt->resampler = NULL;
    }

    if (bt->data != NULL) {
        free(bt->data);
        bt->data = NULL;
    }
}

static uint32_t out_get_latency(struct a2dp_stream_out * out)
{
    int latency_us;

    ALOGV("%s",__FUNCTION__);

    latency_us = ((out->common.buffer_sz * 1000 ) /
                    audio_stream_out_frame_size(&out->stream) /
                    out->common.cfg.rate) * 1000;


    return (latency_us / 1000) + 200;
}

static int calc_audiotime(struct a2dp_config cfg, int bytes)
{
    int chan_count = popcount(cfg.channel_flags);
    int bytes_per_sample = 4;

    ASSERTC(cfg.format == AUDIO_FORMAT_PCM_8_24_BIT,
            "unsupported sample sz", cfg.format);

    return (int)(((int64_t)bytes * (1000000 / (chan_count * bytes_per_sample))) / cfg.rate);
}

static ssize_t out_write(struct audio_stream_out *stream, const void* buffer,
                         size_t bytes)
{
    struct a2dp_stream_out *out = (struct a2dp_stream_out *)stream;
    int sent;
    int us_delay;

    ALOGV("write %zu bytes (fd %d)", bytes, out->common.audio_fd);

    pthread_mutex_lock(&out->common.lock);
    if (out->common.state == AUDIO_A2DP_STATE_SUSPENDED ||
            out->common.state == AUDIO_A2DP_STATE_STOPPING) {
        ALOGV("stream suspended or closing");
        goto error;
    }

    /* only allow autostarting if we are in stopped or standby */
    if ((out->common.state == AUDIO_A2DP_STATE_STOPPED) ||
        (out->common.state == AUDIO_A2DP_STATE_STANDBY))
    {
        if (ipc_if->start_audio_datapath(&out->common) < 0)
        {
            goto error;
        }
    }
    else if (out->common.state != AUDIO_A2DP_STATE_STARTED)
    {
        ALOGE("%s: stream not in stopped or standby", __FUNCTION__);
        goto error;
    }

    pthread_mutex_unlock(&out->common.lock);

    sent = ipc_if->skt_write(out->common.audio_fd, buffer,  bytes);
    pthread_mutex_lock(&out->common.lock);

    if (sent == -1)
    {
        ipc_if->skt_disconnect(out->common.audio_fd);
        out->common.audio_fd = AUDIO_SKT_DISCONNECTED;
        if ((out->common.state != AUDIO_A2DP_STATE_SUSPENDED) &&
                (out->common.state != AUDIO_A2DP_STATE_STOPPING)) {
            out->common.state = AUDIO_A2DP_STATE_STOPPED;
        } else {
            ALOGE("%s: write failed : stream suspended, avoid resetting state", __FUNCTION__);
        }
        goto error;
    }

    pthread_mutex_unlock(&out->common.lock);
    return bytes;

error:
    pthread_mutex_unlock(&out->common.lock);
    us_delay = calc_audiotime(out->common.cfg, bytes);
    usleep(us_delay);
    return bytes;
}

static audio_format_t out_get_format(const struct audio_stream *stream)
{
    struct a2dp_stream_out *out = (struct a2dp_stream_out *)stream;
    ALOGV("format 0x%x", out->common.cfg.format);
    return out->common.cfg.format;
}

static uint32_t out_get_channels(const struct audio_stream *stream)
{
    struct a2dp_stream_out *out = (struct a2dp_stream_out *)stream;

    ALOGV("channels 0x%" PRIx32, out->common.cfg.channel_flags);

    return out->common.cfg.channel_flags;
}


int audio_extn_bt_hal_load(void **handle)
{
    hw_module_t *mod;
    int status = 0;
    struct bt_hal_module *bt = NULL;

    ALOGV("%s", __FUNCTION__);

    if (handle == NULL) {
        status = -EINVAL;
        goto EXIT;
    }

    bt = malloc(sizeof(bt_hal_module_t));
    if (bt == NULL){
        ALOGE("%s: Memory allocation failed!", __FUNCTION__);
        status = -ENOMEM;
        goto EXIT;
    }

    *handle = bt;

    status = hw_get_module_by_class(AUDIO_HARDWARE_MODULE_ID/*_A2DP*/,
                                    (const char*)"a2dp",
                                    (const hw_module_t**)&mod);
    if (status) {
        ALOGE("%s: Could not get a2dp hardware module", __FUNCTION__);
        goto EXIT;
    }

    bt->a2dp_device = calloc(1, sizeof(struct audio_hw_device));
    if (!bt->a2dp_device)
        return -ENOMEM;

    bt->a2dp_device->common.module = (struct hw_module_t *) mod;

    if (status) {
        ALOGE("%s: couldn't open a2dp audio hw device", __FUNCTION__);
        goto EXIT;
    }

EXIT:
    return status;
}

int audio_extn_bt_hal_unload(void *handle)
{
    int status = 0;
    struct bt_hal_module *bt = NULL;

    ALOGV("%s", __FUNCTION__);

    if (handle == NULL) {
        status = -EINVAL;
        goto EXIT;
    }

    bt = (struct bt_hal_module *) handle;

    if (!bt->a2dp_device) {
        ALOGE("%s: No active A2dp output found", __FUNCTION__);
        goto EXIT;
    }

    if (bt->a2dp_output != NULL) {
        audio_extn_bt_hal_close_output_stream(bt);
        bt->a2dp_output = NULL;
    }

    if (bt->reformatted_buf != NULL) {
        free(bt->reformatted_buf);
        bt->reformatted_buf = NULL;
    }

    free(bt->a2dp_device);

EXIT:
    return status;
}

int audio_extn_bt_hal_open_output_stream(void *handle, int in_rate, audio_channel_mask_t channel_mask, int bit_width)
{
    int status = 0;
    struct bt_hal_module *bt = NULL;

    ALOGV("%s", __FUNCTION__);

    if (handle == NULL) {
        status = -EINVAL;
        goto EXIT;
    }

    bt = (struct bt_hal_module *) handle;

    if (bt->a2dp_device == NULL) {
        ALOGE("%s: Invalid device instance!", __FUNCTION__);
        status = -EINVAL;
        goto EXIT;
    }

    if (channel_mask != AUDIO_CHANNEL_OUT_STEREO && channel_mask != AUDIO_CHANNEL_OUT_MONO) {
        status = -EINVAL;
        goto EXIT;
    }

    if (bit_width != CODEC_BACKEND_DEFAULT_BIT_WIDTH) {
        status = -EINVAL;
        goto EXIT;
    }

    bt->in_channel_mask = channel_mask;
    bt->bit_width = bit_width;

    bt->a2dp_output = (struct a2dp_stream_out *)calloc(1, sizeof(struct a2dp_stream_out));

    if (!bt->a2dp_output) {
        status = -ENOMEM;
        goto EXIT;
    }

    lib_handle = dlopen("libbthost_if.so", RTLD_NOW);
    if (!lib_handle)
    {
        ALOGV("Failed to load bthost-ipc library %s",dlerror());
        status = -EINVAL;
        goto EXIT;
    }
    else
    {
        ipc_if = (bt_host_ipc_interface_t*) dlsym(lib_handle,"BTHOST_IPC_INTERFACE");
        if (!ipc_if)
        {
            ALOGE("%s: Failed to load BT IPC library symbol", __FUNCTION__);
            status  = -EINVAL;
            goto EXIT;
        }
    }

    bt->a2dp_output->stream.common.get_channels = out_get_channels;
    bt->a2dp_output->stream.common.get_format = out_get_format;
    bt->a2dp_output->stream.write = out_write;

    /* initialize a2dp specifics */
    ipc_if->a2dp_stream_common_init(&bt->a2dp_output->common);
    bt->a2dp_output->common.cfg.channel_flags = AUDIO_STREAM_DEFAULT_CHANNEL_FLAG;
    bt->a2dp_output->common.cfg.format = AUDIO_FORMAT_PCM_8_24_BIT;
    bt->a2dp_output->common.cfg.rate = AUDIO_STREAM_DEFAULT_RATE;

    /* set output bt->config values */

    bt->config.format = bt->a2dp_output->common.cfg.format;
    bt->config.sample_rate = bt->a2dp_output->common.cfg.rate;
    bt->config.channel_mask = bt->a2dp_output->common.cfg.channel_flags;

    ipc_if->a2dp_open_ctrl_path(&bt->a2dp_output->common);

    if (bt->a2dp_output->common.ctrl_fd == AUDIO_SKT_DISCONNECTED)
    {
        ALOGE("%s: ctrl socket failed to connect (%s)", __FUNCTION__, strerror(errno));
        status = -EINVAL;
        goto EXIT;
    }

    if (ipc_if->a2dp_command(&bt->a2dp_output->common, A2DP_CTRL_CMD_OFFLOAD_NOT_SUPPORTED) == 0) {
        ALOGI("Streaming mode set successfully");
    }
    /* Delay to ensure Headset is in proper state when START is initiated
       from DUT immediately after the connection due to ongoing music playback. */
    usleep(250000);
    if (status) {
        ALOGE("%s: Failed to open output stream for a2dp: status %d", __FUNCTION__, status);
        goto EXIT;
    } else {

        if (in_rate != 44100) {
            bt_create_resampler(in_rate, bt);
        }
        if (bt->config.format != AUDIO_FORMAT_PCM_16_BIT) {
            bt->reformatted_buf = malloc(DEFAULT_BUF_SIZE * 4);
            if (bt->reformatted_buf == NULL) {
                ALOGE("%s: memory allocation failed!", __FUNCTION__);
                status = -ENOMEM;
                goto EXIT;
            }
        }
    }

EXIT:
    if (status != 0 && bt->a2dp_output != NULL) {
        free(bt->a2dp_output);
        bt->a2dp_output = NULL;
    }
    return status;
}

int audio_extn_bt_hal_close_output_stream(void *handle)
{
    int status = 0;
    struct bt_hal_module *bt = NULL;

    ALOGV("%s", __FUNCTION__);

    if (handle == NULL) {
        status = -EINVAL;
        goto EXIT;
    }

    bt = (struct bt_hal_module *) handle;

    if (bt->a2dp_device == NULL) {
        ALOGI("%s: No active A2dp output found", __FUNCTION__);
        bt->a2dp_output = NULL;
        goto EXIT;
    }

    if (bt->a2dp_output == NULL) {
        ALOGE("%s: Invalid A2DP stream instance!", __FUNCTION__);
        status = -EINVAL;
        goto EXIT;
    }

    bt_destroy_resampler(bt);

    pthread_mutex_lock(&bt->a2dp_output->common.lock);
    if ((bt->a2dp_output->common.state == AUDIO_A2DP_STATE_STARTED) ||
            (bt->a2dp_output->common.state == AUDIO_A2DP_STATE_STOPPING))
        ipc_if->stop_audio_datapath(&bt->a2dp_output->common);

    ipc_if->skt_disconnect(bt->a2dp_output->common.ctrl_fd);
    bt->a2dp_output->common.ctrl_fd = AUDIO_SKT_DISCONNECTED;
    if (lib_handle)
        dlclose(lib_handle);
    free(bt->a2dp_output);
    pthread_mutex_unlock(&bt->a2dp_output->common.lock);
    bt->a2dp_output = NULL;

    if (bt->reformatted_buf != NULL) {
        free(bt->reformatted_buf);
        bt->reformatted_buf = NULL;
    }
EXIT:
    return status;
}

int audio_extn_bt_hal_out_write(void *handle, void *buf, int size)
{
    int status = 0;
    size_t in_frames = 0;
    size_t out_frames = 0;
    struct bt_hal_module *bt = NULL;

    ALOGV("%s", __FUNCTION__);

    if (handle == NULL) {
        status = -EINVAL;
        goto EXIT;
    }

    bt = (struct bt_hal_module *) handle;

    in_frames = size/(popcount(bt->in_channel_mask) * (bt->bit_width / 8));

    if (bt->resampler != NULL) {
        if (bt->data == NULL) {
            bt->data = malloc(size);
            if (bt->data == NULL){
                ALOGE("%s: Memory Allocation failed!", __FUNCTION__);
                status = -EINVAL;
                goto EXIT;
            }
        }
        out_frames = in_frames;
        bt->resampler->resample_from_input(bt->resampler,
                                           (int16_t *)buf,
                                           &in_frames,
                                           (int16_t *)bt->data,
                                           &out_frames);

        if (out_frames > 0) {
            ALOGV("%s: writing %d bytes to BT device", __FUNCTION__, (int) (out_frames * popcount(bt->in_channel_mask) * (bt->bit_width / 8)));

            if (bt->reformatted_buf != NULL) {
                if (size > DEFAULT_BUF_SIZE) {
                    bt->reformatted_buf = realloc(bt->reformatted_buf, size * 4);
                    if (bt->reformatted_buf == NULL) {
                        status = -ENOMEM;
                        goto EXIT;
                    }
                }

                memcpy_by_audio_format(bt->reformatted_buf, bt->config.format, bt->data, AUDIO_FORMAT_PCM_16_BIT, out_frames * popcount(bt->in_channel_mask));
                (bt->a2dp_output)->stream.write(&bt->a2dp_output->stream, bt->reformatted_buf, out_frames * popcount(bt->in_channel_mask) * (bt->bit_width/8) * 2);
            } else {
                (bt->a2dp_output)->stream.write(&bt->a2dp_output->stream, bt->data, (out_frames * popcount(bt->in_channel_mask) * (bt->bit_width / 8)));
            }
        }
    } else {
        if (bt->reformatted_buf != NULL) {
            if (size > DEFAULT_BUF_SIZE) {
                bt->reformatted_buf = realloc(bt->reformatted_buf, size * 4);
                if (bt->reformatted_buf == NULL) {
                    status = -ENOMEM;
                    goto EXIT;
                }
            }

            memcpy_by_audio_format(bt->reformatted_buf, bt->config.format, buf, AUDIO_FORMAT_PCM_16_BIT, in_frames * popcount(bt->in_channel_mask));
            (bt->a2dp_output)->stream.write(&bt->a2dp_output->stream, bt->reformatted_buf, size * 2);
        } else {
            (bt->a2dp_output)->stream.write(&bt->a2dp_output->stream, buf, size);
        }
    }

EXIT:
    return status;
}

int audio_extn_bt_hal_get_latency(void *handle) {
    int status = 0;
    struct bt_hal_module *bt = NULL;
    ALOGV("%s", __FUNCTION__);

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

    bt = (struct bt_hal_module *) handle;
    if (bt->a2dp_output != NULL) {
        status = out_get_latency(bt->a2dp_output);
    } else {
        status = -EINVAL;
    }

    return status;
}

struct audio_stream_out *audio_extn_bt_hal_get_output_stream(void *handle)
{
    struct bt_hal_module *bt = NULL;

    ALOGV("%s", __FUNCTION__);

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

    bt = (struct bt_hal_module *) handle;

    return (bt->a2dp_output == NULL)? NULL: &bt->a2dp_output->stream;
}

void *audio_extn_bt_hal_get_device(void *handle)
{
    int status = 0;
    struct bt_hal_module *bt = NULL;

    ALOGV("%s", __FUNCTION__);

    if (handle == NULL) {
        status = -EINVAL;
        goto EXIT;
    }

    bt = (struct bt_hal_module *) handle;

    return bt->a2dp_device;

EXIT:
    return NULL;
}
