/*
 * Copyright (c) Hisilicon Technologies Co., Ltd. 2019-2019. All rights reserved.
 * Description: MPI function file for Huanglong low latency
 * Author: audio
 * Create: 2019-05-30
 */

#include <sys/ioctl.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>

#include "mpi_ao_plugin.h"
#include "mpi_ao_debug.h"

#include "securec.h"
#include "list.h"
#include "mpi_memory_ext.h"
#include "mpi_ao_ext.h"
#include "soc_errno.h"

#include "drv_ioctl_ao.h"
#include "mpi_ao_circ_buf.h"
#include "mpi_ao.h"

#ifdef __cplusplus
#if __cplusplus
extern "C" {
#endif
#endif /* __cplusplus */

#define MAX_BUFFER_MS 500
#define PAGE_SIZE_MASK 0xfffff000
#define WRITE_REG_OFFSET 0x10
#define READ_REG_OFFSET  0x14
#define OFFSET 0x08
#define FRAME_SAMPLES_MIN 256
#define FRAME_SAMPLES_MASK 0x1F00 /* support 256/512/1024/2048/4096 samples */
#define FRONTOUT_EXTRA_FRAME_SIZE  (6 * sizeof(td_u16) * 2048)

#define FRONTOUT_WPTR_OFFSET        0x00000088
#define FRONTOUT_RPTR_OFFSET        0x00000090
#define BACKIN_WPTR_OFFSET          0x000000a4
#define BACKIN_RPTR_OFFSET          0x000000ac
#define AOE_REG_PLUGIN_ATTR_OFFSET      0x000000b4
#define FRONTOUT_FRAME_CHANNELS      6

typedef enum {
    FRONT_OUT,
    BACK_IN,
} frame_direction;

typedef struct {
    td_bool used;
    td_bool enable;
    ao_snd_id snd;
    pthread_mutex_t mutex;

    td_s32 bit_depth;
    td_u32 sample_rate;
    td_u32 channels;
    ext_audio_buffer aoe_reg;
    ext_audio_buffer frontout_buf;
    ext_audio_buffer backin_buf;
    circ_buf frontout_cb;
    circ_buf backin_cb;
    td_u32 frontout_buf_used_size;
    td_u32 frontout_buf_extra_frame_size;
    td_u32 frame_samples_diff;
} snd_engine_plugin_source;

static snd_engine_plugin_source g_engine_state = {
    .used = TD_FALSE,
    .enable = TD_FALSE,
    .snd = AO_SND_0,
    .bit_depth = EXT_BIT_DEPTH_16,
    .channels = FRONTOUT_FRAME_CHANNELS,
    .sample_rate = EXT_SAMPLE_RATE_48K,
};

static pthread_mutex_t g_engine_plugin_mutex = PTHREAD_MUTEX_INITIALIZER;

static inline td_void engine_plugin_mutex_lock(td_void)
{
    if (pthread_mutex_lock(&g_engine_plugin_mutex) != 0) {
        soc_log_err("Lock mutex failed\n");
    }
}

static inline td_void engine_plugin_mutex_unlock(td_void)
{
    if (pthread_mutex_unlock(&g_engine_plugin_mutex) != 0) {
        soc_log_err("Unlock mutex failed\n");
    }
}

static td_void audio_munmap(td_void *buffer, td_u32 size)
{
    td_s32 ret;

    if (buffer == TD_NULL) {
        return;
    }

    ret = munmap(buffer, size);
    if (ret != TD_SUCCESS) {
        soc_err_print_call_fun_err(munmap, ret);
        return;
    }
}

static td_void *audio_mmap(td_s32 fd, td_u32 size, td_u64 phys_addr)
{
    td_void *buffer = TD_NULL;

    if (fd < 0) {
        return TD_NULL;
    }
    buffer = (td_void *)mmap64((td_void *)0, size,
        PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off64_t)phys_addr);
    if (buffer == MAP_FAILED) {
        soc_log_err("mmap failed, fd = %d, size = %d\n", fd, size);
        return TD_NULL;
    }
    return buffer;
}

static td_void audio_munmap_ion(td_void *buf, td_u32 size)
{
    audio_munmap(buf, size);
}

static td_s32 buf_mmap(ext_audio_buffer *map_buf)
{
    map_buf->virt_addr = audio_mmap(map_buf->fd, map_buf->size, 0);
    if (map_buf->virt_addr == TD_NULL) {
        return SOC_ERR_AO_MALLOC_FAILED;
    }

    return TD_SUCCESS;
}

static td_void buf_munmap(ext_audio_buffer *map_buf)
{
    audio_munmap_ion(map_buf->virt_addr, map_buf->size);
    map_buf->fd = -1;
    map_buf->virt_addr = TD_NULL;
}

static inline td_void munmap_hw_aoe_reg(td_void)
{
    audio_munmap(g_engine_state.aoe_reg.virt_addr, g_engine_state.aoe_reg.size);
}

static td_s32 mmap_hw_aoe_reg(td_void)
{
    td_s32 fd;
    ext_audio_buffer *buf = TD_NULL;

    /* hw aoe use dsp dram or cpu sram as aoe reg
     * we use ao fd to mmap
     */
    fd = mpi_ao_get_fd();
    if (fd < 0) {
        soc_log_err("mpi_ao_get_fd failed\n");
        return SOC_ERR_AO_DEV_NOT_OPEN;
    }

    buf = &g_engine_state.aoe_reg;
    buf->fd = fd;
    buf->virt_addr = (td_u8 *)audio_mmap(fd, buf->size, buf->phys_addr);
    if (buf->virt_addr == TD_NULL) {
        soc_log_err("call audio_mmap failed\n");
        return SOC_ERR_AO_MALLOC_FAILED;
    }

    return TD_SUCCESS;
}

static td_void snd_engine_plugin_munmap_buffer(td_void)
{
    snd_engine_plugin_source *engine_state = &g_engine_state;
    buf_munmap(&engine_state->backin_buf);
    buf_munmap(&engine_state->frontout_buf);
    munmap_hw_aoe_reg();
}

static td_void get_mmap_buffer_info(const mixer_plugin_attr *attr)
{
    snd_engine_plugin_source *engine_state = &g_engine_state;

    engine_state->aoe_reg.phys_addr = attr->aoe_reg.phys_addr;
    engine_state->aoe_reg.size = attr->aoe_reg.size;
    engine_state->aoe_reg.fd = attr->aoe_reg.fd;

    engine_state->frontout_buf.phys_addr = attr->frontout_buffer.phys_addr;
    engine_state->frontout_buf.size = attr->frontout_buffer.size + FRONTOUT_EXTRA_FRAME_SIZE;
    engine_state->frontout_buf_used_size = attr->frontout_buffer.size;
    engine_state->frontout_buf_extra_frame_size = FRONTOUT_EXTRA_FRAME_SIZE;
    engine_state->frontout_buf.fd = attr->frontout_buffer.fd;

    engine_state->backin_buf.phys_addr = attr->backin_buffer.phys_addr;
    engine_state->backin_buf.size = attr->backin_buffer.size;
    engine_state->backin_buf.fd = attr->backin_buffer.fd;
}

static td_s32 snd_engine_plugin_mmap_buffer(td_void)
{
    td_s32 ret;
    td_u32 *write_ptr = TD_NULL;
    td_u32 *read_ptr = TD_NULL;
    snd_engine_plugin_source *engine_state = &g_engine_state;

    ret = mmap_hw_aoe_reg();
    if (ret != TD_SUCCESS) {
        soc_err_print_call_fun_err(mmap_hw_aoe_reg, ret);
        return ret;
    }

    ret = buf_mmap(&engine_state->frontout_buf);
    if (ret != TD_SUCCESS) {
        soc_err_print_call_fun_err(buf_mmap, ret);
        munmap_hw_aoe_reg();
        return ret;
    }

    ret = buf_mmap(&engine_state->backin_buf);
    if (ret != TD_SUCCESS) {
        soc_err_print_call_fun_err(buf_mmap, ret);
        buf_munmap(&engine_state->frontout_buf);
        munmap_hw_aoe_reg();
        return ret;
    }

    write_ptr = (td_u32 *)(engine_state->aoe_reg.virt_addr + FRONTOUT_WPTR_OFFSET);
    read_ptr = (td_u32 *)(engine_state->aoe_reg.virt_addr + FRONTOUT_RPTR_OFFSET);
    soc_log_dbg("frontout v %p s %d w %p r %p\n",
        engine_state->frontout_buf.virt_addr, engine_state->frontout_buf_used_size, write_ptr, read_ptr);
    circ_buf_init(&engine_state->frontout_cb,
                  (td_u32 *)(write_ptr),
                  (td_u32 *)(read_ptr),
                  engine_state->frontout_buf.virt_addr,
                  engine_state->frontout_buf_used_size);

    write_ptr = (td_u32 *)(engine_state->aoe_reg.virt_addr + BACKIN_WPTR_OFFSET);
    read_ptr = (td_u32 *)(engine_state->aoe_reg.virt_addr + BACKIN_RPTR_OFFSET);
    soc_log_dbg("backin v %p s %d w %p r %p\n",
        engine_state->backin_buf.virt_addr, engine_state->backin_buf.size, write_ptr, read_ptr);
    circ_buf_init(&engine_state->backin_cb,
                  (td_u32 *)(write_ptr),
                  (td_u32 *)(read_ptr),
                  engine_state->backin_buf.virt_addr,
                  engine_state->backin_buf.size);

    return TD_SUCCESS;
}

static td_u32 aoe_reg_read_frontout_channel(const snd_engine_plugin_source *engine_state)
{
    td_u32 ch = 0;
    td_u32 engine_plugin_attr = 0;
    td_u32 *engine_plugin_attr_reg = TD_NULL;
    const td_u32 frontout_ch_map[] = { 0, EXT_AUDIO_CH_STEREO, EXT_AUDIO_CH_6, 0};

    if (engine_state->aoe_reg.virt_addr == TD_NULL) {
        soc_log_err("engine_state->aoe_reg.virt_addr null pointer!\n");
        return 0;
    }

    engine_plugin_attr_reg = (td_u32 *)(engine_state->aoe_reg.virt_addr + AOE_REG_PLUGIN_ATTR_OFFSET);
    engine_plugin_attr = *engine_plugin_attr_reg;
    ch = engine_plugin_attr & 0x0003; /* aoe reg: engine_plugin_attr.bits.ch */
    if (ch >= sizeof(frontout_ch_map) / sizeof(td_u32)) {
        soc_log_warn("channels is error %d\n", ch);
        return 0;
    }

    return frontout_ch_map[ch];
}

static td_s32 check_get_buffer_params_valid(td_u32 require_size, td_u32 timeout_ms)
{
    if (require_size == 0) {
        return SOC_ERR_AO_INVALID_PARA;
    }
    if (timeout_ms > MAX_BUFFER_MS) {
        return SOC_ERR_AO_INVALID_PARA;
    }
    return TD_SUCCESS;
}

static td_s32 get_engine_plugin_frame_info(ext_audio_mixer_plugin_frame *frame, frame_direction dir)
{
    snd_engine_plugin_source *engine_state = &g_engine_state;
    if (dir == FRONT_OUT) {
        frame->frame_addr.frame_fd.mem_handle = engine_state->frontout_buf.fd;
        frame->frame_addr.frame_size = engine_state->frontout_buf_used_size;
    } else if (dir == BACK_IN) {
        frame->frame_addr.frame_fd.mem_handle = engine_state->backin_buf.fd;
        frame->frame_addr.frame_size = engine_state->backin_buf.size;
    }

    frame->frame_info.bit_depth = engine_state->bit_depth;
    frame->frame_info.channels = aoe_reg_read_frontout_channel(engine_state);
    engine_state->channels = frame->frame_info.channels;
    frame->frame_info.sample_rate = engine_state->sample_rate;
    frame->frame_info.interleaved = TD_TRUE;
    return TD_SUCCESS;
}

static td_s32 snd_engine_plugin_enable(ao_snd_id sound)
{
    td_s32 ret, ao_fd;
    engine_plugin_mutex_lock();
    ao_snd_mixer_plugin_buffer_param param;
    ao_snd_mixer_plugin_enable_param enable;

    if (g_engine_state.used == TD_TRUE) {
        soc_log_err("SND engine plugin is busy!\n");
        engine_plugin_mutex_unlock();
        return SOC_ERR_AO_NOTSUPPORT;
    }

    ao_fd = mpi_ao_get_fd();
    if (ao_fd < 0) {
        soc_log_err("mpi_ao_get_fd failed\n");
        engine_plugin_mutex_unlock();
        return SOC_ERR_AO_DEV_NOT_OPEN;
    }

    ret = memset_s(&param, sizeof(param), 0, sizeof(ao_snd_mixer_plugin_buffer_param));
    if (ret != EOK) {
        soc_err_print_call_fun_err(memset_s, ret);
        engine_plugin_mutex_unlock();
        return ret;
    }

    enable.sound = sound;
    enable.plugin_enable = TD_TRUE;
    ret = ioctl(ao_fd, CMD_AO_MIXER_PLUGIN_SET_ENABLE, &enable);
    if (ret != TD_SUCCESS) {
        soc_log_err("ioctl CMD_AO_MIXER_PLUGIN_SET_ENABLE TRUE failed(0x%x)\n", ret);
        engine_plugin_mutex_unlock();
        return ret;
    }

    param.sound = sound;
    ret = ioctl(ao_fd, CMD_AO_MIXER_PLUGIN_GET_BUFFER_INFO, &param);
    if (ret != TD_SUCCESS) {
        soc_log_err("ioctl CMD_AO_MIXER_PLUGIN_GET_BUFFER_INFO failed(0x%x)\n", ret);
        engine_plugin_mutex_unlock();
        return ret;
    }

    get_mmap_buffer_info(&param.buffer_attr);

    ret = snd_engine_plugin_mmap_buffer();
    if (ret != TD_SUCCESS) {
        soc_err_print_call_fun_err(snd_engine_plugin_mmap_buffer, ret);
        engine_plugin_mutex_unlock();
        return ret;
    }
    g_engine_state.enable = TD_TRUE;
    g_engine_state.used = TD_TRUE;
    soc_log_notice("set engine plugin enable\n");
    engine_plugin_mutex_unlock();
    return TD_SUCCESS;
}

static td_s32 snd_engine_plugin_disable(ao_snd_id sound)
{
    td_s32 ret;
    td_s32 ao_fd;
    ao_snd_mixer_plugin_enable_param enable;
    ao_snd_mixer_plugin_buffer_param param;

    engine_plugin_mutex_lock();

    ao_fd = mpi_ao_get_fd();
    if (ao_fd < 0) {
        soc_log_err("mpi_ao_get_fd failed\n");
        engine_plugin_mutex_unlock();
        return SOC_ERR_AO_DEV_NOT_OPEN;
    }

    param.sound = sound;
    ret = ioctl(ao_fd, CMD_AO_MIXER_PLUGIN_GET_BUFFER_INFO, &param);
    if (ret != TD_SUCCESS) {
        soc_log_err("ioctl CMD_AO_MIXER_PLUGIN_GET_BUFFER_INFO failed(0x%x)\n", ret);
        engine_plugin_mutex_unlock();
        return ret;
    }

    g_engine_state.enable = TD_FALSE;
    g_engine_state.used = TD_FALSE;
    get_mmap_buffer_info(&param.buffer_attr);
    snd_engine_plugin_munmap_buffer();

    enable.sound = sound;
    enable.plugin_enable = TD_FALSE;
    ret = ioctl(ao_fd, CMD_AO_MIXER_PLUGIN_SET_ENABLE, &enable);
    if (ret != TD_SUCCESS) {
        soc_log_err("ioctl CMD_AO_MIXER_PLUGIN_SET_ENABLE FALSE failed(0x%x)\n", ret);
        engine_plugin_mutex_unlock();
        return ret;
    }

    soc_log_notice("set engine plugin disable\n");
    engine_plugin_mutex_unlock();
    return TD_SUCCESS;
}

td_s32 snd_mixer_set_engine_plugin_enable(ao_snd_id sound, td_bool enable)
{
    if (enable == TD_TRUE) {
        return snd_engine_plugin_enable(sound);
    } else {
        return snd_engine_plugin_disable(sound);
    }
}

td_s32 snd_mixer_get_engine_plugin_enable(ao_snd_id sound, td_bool *enable)
{
    td_s32 ret;
    td_s32 ao_fd;
    ao_snd_mixer_plugin_enable_param params;

    if (sound != AO_SND_0) {
        return SOC_ERR_AO_INVALID_ID;
    }
    if (enable == TD_NULL) {
        return SOC_ERR_AO_NULL_PTR;
    }

    ao_fd = mpi_ao_get_fd();
    if (ao_fd < 0) {
        soc_log_err("mpi_ao_get_fd failed\n");
        return SOC_ERR_AO_DEV_NOT_OPEN;
    }

    params.sound = sound;
    ret = ioctl(ao_fd, CMD_AO_MIXER_PLUGIN_GET_ENABLE, &params);
    if (ret != TD_SUCCESS) {
        soc_log_err("ioctl CMD_AO_MIXER_PLUGIN_GET_ENABLE failed(0x%x)\n", ret);
        return ret;
    }

    g_engine_state.enable = params.plugin_enable;
    g_engine_state.used = params.plugin_enable;
    *enable = params.plugin_enable;
    return TD_SUCCESS;
}

td_s32 snd_mixer_acquire_engine_plugin_frontout_stream(ao_snd_id sound, td_u32 require_samples,
    ext_audio_mixer_plugin_frame *frontout_frame, td_u32 timeout_ms)
{
    td_s32 ret;
    circ_buf_info info;
    td_u32 wait_ms = 0;
    td_u32 busy_size, dest_size, require_size, realtime_temp;
    ext_audio_mixer_plugin_frame out_frame;
    snd_engine_plugin_source *engine_state = &g_engine_state;

    if (engine_state->enable == TD_FALSE) {
        return SOC_ERR_AO_DEV_NOT_OPEN;
    }

    TD_UNUSED(sound);
    ret = check_get_buffer_params_valid(require_samples, timeout_ms);
    if (ret != TD_SUCCESS) {
        soc_err_print_call_fun_err(check_get_buffer_params_valid, ret);
        return ret;
    }

    circ_buf_query_info(&engine_state->frontout_cb, &info);
    soc_log_dbg("out w %d r %d b %d f %d\n", info.write_pos, info.read_pos, info.total_data_size,
        info.total_free_size);

    realtime_temp = (require_samples & FRAME_SAMPLES_MASK) * sizeof(td_s16);
    if (realtime_temp == 0) {
        soc_log_err("query buffer samples invalid %d\n", require_samples);
        return SOC_ERR_AO_INVALID_PARA;
    }
    require_size = realtime_temp * FRONTOUT_FRAME_CHANNELS;
    do {
        busy_size = circ_buf_query_busy(&engine_state->frontout_cb);
        if ((require_size != 0) && (busy_size >= require_size)) {
            break;
        } else if (wait_ms++ >= timeout_ms) {
            break;
        }
        usleep(1000); /* 1000 is sleep time */
    } while (wait_ms <= timeout_ms);

    if ((wait_ms >= timeout_ms) && (busy_size < require_size)) {
        soc_log_warn("query buf_data time out! need_bytes(0x%x), avail_bytes(0x%x)\n", require_size, busy_size);
        return SOC_ERR_AO_INBUF_EMPTY;
    }

    get_engine_plugin_frame_info(&out_frame, FRONT_OUT);
    dest_size = require_size < busy_size ? require_size : busy_size;
    out_frame.frame_addr.frame_size = dest_size;
    out_frame.frame_addr.frame_fd.addr_offset = *(engine_state->frontout_cb.read);
    out_frame.frame_info.pcm_samples = require_samples;
    ret = memcpy_s(frontout_frame, sizeof(ext_audio_mixer_plugin_frame), &out_frame, sizeof(out_frame));
    if (ret != EOK) {
        soc_err_print_call_fun_err(memcpy_s, ret);
        return ret;
    }
    return TD_SUCCESS;
}

td_s32 snd_mixer_release_engine_plugin_frontout_stream(ao_snd_id sound,
    const ext_audio_mixer_plugin_frame *frontout_frame)
{
    snd_engine_plugin_source *engine_state = &g_engine_state;

    if (engine_state->enable == TD_FALSE) {
        return SOC_ERR_AO_DEV_NOT_OPEN;
    }

    if (frontout_frame == TD_NULL) {
        return SOC_ERR_AO_NULL_PTR;
    }

    TD_UNUSED(sound);
    circ_buf_update_read_pos(&engine_state->frontout_cb, frontout_frame->frame_addr.frame_size);
    engine_state->frame_samples_diff += frontout_frame->frame_info.pcm_samples;
    return TD_SUCCESS;
}

td_s32 snd_mixer_get_engine_plugin_backin_buffer(ao_snd_id sound, td_u32 require_size,
    ext_audio_mixer_plugin_frame *backin_frame, td_u32 timeout_ms)
{
    td_s32 ret;
    circ_buf_info info;
    td_u32 free_size;
    td_u32 dest_size;
    td_u32 wait_ms = 0;
    ext_audio_mixer_plugin_frame in_frame;
    snd_engine_plugin_source *engine_state = &g_engine_state;

    if (engine_state->enable == TD_FALSE) {
        return SOC_ERR_AO_DEV_NOT_OPEN;
    }

    TD_UNUSED(sound);
    ret = check_get_buffer_params_valid(require_size, timeout_ms);
    if (ret != TD_SUCCESS) {
        soc_err_print_call_fun_err(check_get_buffer_params_valid, ret);
        return ret;
    }
    get_engine_plugin_frame_info(&in_frame, BACK_IN);

    circ_buf_query_info(&engine_state->backin_cb, &info);
    soc_log_dbg("in w %d r %d b %d f %d req %d\n", info.write_pos, info.read_pos,
        info.total_data_size, info.total_free_size, require_size);
    do {
        free_size = circ_buf_query_free(&engine_state->backin_cb);
        if (free_size > require_size) {
            break;
        } else if (wait_ms++ >= timeout_ms) {
            break;
        }
        usleep(1000); /* 1000 is sleep time */
    } while (wait_ms <= timeout_ms);

    if ((wait_ms >= timeout_ms) && (free_size <= require_size)) {
        soc_log_warn("query buf_data time out! need_bytes(0x%x), avail_bytes(0x%x)\n", require_size, free_size);
        return SOC_ERR_AO_OUT_BUF_FULL;
    }
    dest_size = require_size < free_size ? require_size : free_size;
    in_frame.frame_addr.frame_size = dest_size;
    in_frame.frame_addr.frame_fd.addr_offset = *(engine_state->backin_cb.write);
    in_frame.frame_info.pcm_samples = FRAME_SAMPLES_MIN;
    ret = memcpy_s(backin_frame, sizeof(ext_audio_mixer_plugin_frame), &in_frame, sizeof(in_frame));
    if (ret != EOK) {
        soc_err_print_call_fun_err(memcpy_s, ret);
        return ret;
    }
    return TD_SUCCESS;
}

td_s32 snd_mixer_put_engine_plugin_backin_buffer(ao_snd_id sound,
    const ext_audio_mixer_plugin_frame *backin_frame)
{
    snd_engine_plugin_source *engine_state = &g_engine_state;

    if (engine_state->enable == TD_FALSE) {
        return SOC_ERR_AO_DEV_NOT_OPEN;
    }

    if (backin_frame == TD_NULL) {
        return SOC_ERR_AO_NULL_PTR;
    }

    TD_UNUSED(sound);
    circ_buf_update_write_pos(&engine_state->backin_cb, backin_frame->frame_addr.frame_size);
    engine_state->frame_samples_diff -= backin_frame->frame_info.pcm_samples;
    soc_log_info("samples frame store %d \n", engine_state->frame_samples_diff);
    return TD_SUCCESS;
}

#ifdef __cplusplus
#if __cplusplus
}
#endif
#endif /* __cplusplus */