/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#ifdef A2DP_CP_ACCEL

#include "a2dp_decoder_internal.h"
#include "a2dp_decoder_cp.h"
#include "cp_accel.h"
#include "hal_location.h"
#include "hal_timer.h"
#include "hal_trace.h"
#include "heap_api.h"
#include "norflash_api.h"

#define CP_IN_FRAME_CNT                 100
#define CP_OUT_FRAME_CNT                3

#define CP_IN_CACHE_SIZE                (1024 * 10)

#if defined(A2DP_LHDC_V3)
#define CP_HEAP_SIZE                    (1024 * 64)
#else
#define CP_HEAP_SIZE                    (1024 * 32)
#endif

enum CP_DEC_STATE_T {
    CP_DEC_STATE_IDLE,
    CP_DEC_STATE_WORKING,
    CP_DEC_STATE_DONE,
    CP_DEC_STATE_INIT_DONE,
};

struct CP_IN_FRAME_INFO_T {
    uint32_t pos;
    uint32_t len;
};

struct CP_OUT_FRAME_INFO_T {
    enum CP_DEC_STATE_T state;
    uint32_t pos;
    uint32_t len;
};

static CP_BSS_LOC uint32_t cp_heap_buf[CP_HEAP_SIZE / 4];
static CP_BSS_LOC heap_handle_t cp_heap;

static CP_BSS_LOC uint16_t cp_in_widx;
static CP_BSS_LOC uint16_t cp_in_ridx;

static CP_BSS_LOC uint8_t cp_out_widx;
static CP_BSS_LOC uint8_t cp_out_ridx;

static CP_BSS_LOC struct CP_IN_FRAME_INFO_T *in_frames;
static CP_BSS_LOC struct CP_OUT_FRAME_INFO_T *out_frames;

static CP_BSS_LOC uint8_t *cp_in_cache;
static CP_BSS_LOC uint8_t *cp_out_cache;

static CP_BSS_LOC bool reset_frames;

#ifdef A2DP_TRACE_CP_DEC_TIME
static CP_BSS_LOC uint32_t cp_last_dec_time;
#endif

static uint8_t max_buffer_frames = 2;
static bool mcu_dec_inited;
static A2DP_CP_DECODE_T decode_frame;
static enum CP_PROC_DELAY_T proc_delay;

static bool cp_need_reset;

CP_TEXT_SRAM_LOC
unsigned int set_cp_reset_flag(uint8_t evt)
{
    cp_need_reset = true;
    return 0;
}

bool is_cp_need_reset(void)
{
    bool ret = cp_need_reset;
    cp_need_reset = false;
    return ret;
}

bool is_cp_init_done(void)
{
    return cp_accel_init_done();
}


CP_TEXT_SRAM_LOC
static void reset_frame_info(void)
{
    uint32_t idle_cnt;
    int i;

    if (proc_delay == CP_PROC_DELAY_0_FRAME) {
        idle_cnt = CP_OUT_FRAME_CNT;
    } else if (proc_delay == CP_PROC_DELAY_1_FRAME) {
        idle_cnt = CP_OUT_FRAME_CNT - 1;
    } else {
        idle_cnt = CP_OUT_FRAME_CNT - 2;
    }

    for (i = 0; i < CP_OUT_FRAME_CNT; i++) {
        if (i < idle_cnt) {
            out_frames[i].state = CP_DEC_STATE_IDLE;
        } else {
            out_frames[i].state = CP_DEC_STATE_INIT_DONE;
        }
    }

    cp_in_widx = 0;
    cp_in_ridx = 0;
    cp_out_widx = 0;
    cp_out_ridx = idle_cnt;

    reset_frames = false;
}

CP_TEXT_SRAM_LOC
static unsigned int cp_a2dp_main(uint8_t event)
{
    int ret;
#ifdef A2DP_TRACE_CP_DEC_TIME
    uint32_t stime;
    uint32_t etime;
#endif

    if (!mcu_dec_inited) {
        return 0;
    }

    if (decode_frame) {
        do {
#ifdef A2DP_TRACE_CP_DEC_TIME
            stime = hal_fast_sys_timer_get();
#endif

            ret = decode_frame();

#ifdef A2DP_TRACE_CP_DEC_TIME
            etime = hal_fast_sys_timer_get();
            TRACE_A2DP_DECODER_I("cp_decode: %5u us in %5u us", FAST_TICKS_TO_US(etime - stime), FAST_TICKS_TO_US(etime - cp_last_dec_time));
            cp_last_dec_time = etime;
#endif
        } while (ret == 0);
    }

    if (reset_frames) {
        reset_frame_info();
#ifdef A2DP_TRACE_CP_ACCEL
        TRACE_A2DP_DECODER_I("%s: Reset frames", __func__);
#endif
    }

    return 0;
}

static struct cp_task_desc TASK_DESC_A2DP = {CP_ACCEL_STATE_CLOSED, cp_a2dp_main, NULL, NULL, set_cp_reset_flag};
int a2dp_cp_init(A2DP_CP_DECODE_T decode_func, enum CP_PROC_DELAY_T delay)
{
    if (delay >= CP_PROC_DELAY_QTY) {
        return 1;
    }
    mcu_dec_inited = false;
    decode_frame = decode_func;
    proc_delay = delay;
#ifdef A2DP_TRACE_CP_DEC_TIME
    cp_last_dec_time = hal_fast_sys_timer_get();
#endif

    norflash_api_flush_disable(NORFLASH_API_USER_CP,(uint32_t)cp_accel_init_done);
    cp_accel_open(CP_TASK_A2DP_DECODE, &TASK_DESC_A2DP);
    while(cp_accel_init_done() == false) {
        hal_sys_timer_delay_us(100);
    }
    norflash_api_flush_enable(NORFLASH_API_USER_CP);
    return 0;
}
int a2dp_cp_deinit(void)
{
    cp_accel_close(CP_TASK_A2DP_DECODE);

    return 0;
}

SRAM_TEXT_LOC
int a2dp_cp_decoder_init(uint32_t out_frame_len, uint8_t max_frames)
{
    uint32_t i;
    max_buffer_frames = max_frames;
    if (!mcu_dec_inited) {
#ifdef A2DP_TRACE_CP_ACCEL
        TRACE_A2DP_DECODER_I("%s: Decoder init", __func__);
#endif
        if(cp_accel_init_done() == false){
            TRACE_A2DP_DECODER_I("%s: CP ACCEL not init yet", __func__);
            return 6;
        }

        cp_heap = heap_register(cp_heap_buf, sizeof(cp_heap_buf));
        if (cp_heap == NULL) {
            return 1;
        }

        in_frames = heap_malloc(cp_heap, sizeof(in_frames[0]) * CP_IN_FRAME_CNT);
        if (in_frames == NULL) {
            return 2;
        }

        out_frames = heap_malloc(cp_heap, sizeof(out_frames[0]) * CP_OUT_FRAME_CNT);
        if (out_frames == NULL) {
            return 3;
        }

        cp_in_cache = heap_malloc(cp_heap, CP_IN_CACHE_SIZE);
        if (cp_in_cache == NULL) {
            return 4;
        }

        cp_out_cache = heap_malloc(cp_heap, out_frame_len * CP_OUT_FRAME_CNT);
        if (cp_out_cache == NULL) {
            return 5;
        }

        for (i = 0; i < CP_OUT_FRAME_CNT; i++) {
            out_frames[i].pos = out_frame_len * i;
            out_frames[i].len = out_frame_len;
        }
        reset_frame_info();

        mcu_dec_inited = true;
    }

    return 0;
}

CP_TEXT_SRAM_LOC
static uint32_t get_in_frame_cnt(uint32_t in_widx, uint32_t in_ridx)
{
    uint32_t cnt;

    if (in_widx >= in_ridx) {
        cnt = in_widx - in_ridx;
    } else {
        cnt = CP_IN_FRAME_CNT - in_ridx + in_widx;
    }

    return cnt;
}

uint32_t get_in_cp_frame_cnt(void)
{
    return get_in_frame_cnt(cp_in_widx, cp_in_ridx);
}

uint32_t get_in_cp_frame_delay(void)
{
    return proc_delay;
}

SRAM_TEXT_LOC
static uint32_t get_in_frame_free_cnt(uint32_t in_widx, uint32_t in_ridx)
{
    uint32_t free_cnt;

    if (in_widx >= in_ridx) {
        free_cnt = CP_IN_FRAME_CNT - in_widx + in_ridx;
    } else {
        free_cnt = in_ridx - in_widx;
    }
    free_cnt -= 1;

    return free_cnt;
}

SRAM_TEXT_LOC
int a2dp_cp_put_in_frame(const void *buf1, uint32_t len1, const  void *buf2, uint32_t len2)
{
    uint16_t free_cnt;
    uint16_t in_widx;
    uint16_t in_ridx;
    uint16_t prev_in_widx;
    uint32_t free_len;
    uint32_t in_wpos;
    uint32_t in_rpos;
    uint32_t aligned_len;

    if (reset_frames) {
        return -1;
    }

    in_widx = cp_in_widx;
    in_ridx = cp_in_ridx;

    if (max_buffer_frames < get_in_cp_frame_cnt()){
        return 1;
    }

    free_cnt = get_in_frame_free_cnt(in_widx, in_ridx);
    if (free_cnt == 0) {
        return 1;
    }

    if (in_widx == in_ridx) {
        in_wpos = 0;
        in_rpos = 0;
    } else {
        if (in_widx == 0) {
            prev_in_widx = CP_IN_FRAME_CNT - 1;
        } else {
            prev_in_widx = in_widx - 1;
        }
        in_wpos = in_frames[prev_in_widx].pos + in_frames[prev_in_widx].len;
        if (in_wpos >= CP_IN_CACHE_SIZE) {
            in_wpos -= CP_IN_CACHE_SIZE;
        }
        in_rpos = in_frames[in_ridx].pos;
    }

    // Align to word boundary
    in_wpos = (in_wpos + 3) & ~3;
    if (in_wpos >= CP_IN_CACHE_SIZE) {
        in_wpos -= CP_IN_CACHE_SIZE;
    }
    aligned_len = (len1 + len2 + 3) & ~3;

    if (in_wpos >= in_rpos) {
        free_len = CP_IN_CACHE_SIZE - in_wpos;
        if (in_rpos == 0) {
            free_len -= 1;
        }
        if (free_len < aligned_len) {
            free_len = (in_rpos > 0) ? (in_rpos - 1) : 0;
            if (free_len < aligned_len) {
                return 2;
            }
            in_wpos = 0;
        }
    } else {
        free_len = in_rpos - in_wpos - 1;
        if (free_len < aligned_len) {
            return 3;
        }
    }

    in_frames[in_widx].pos = in_wpos;
    in_frames[in_widx].len = len1 + len2;

    if (len1) {
        memcpy(&cp_in_cache[in_wpos], buf1, len1);
        in_wpos += len1;
    }
    if (len2) {
        memcpy(&cp_in_cache[in_wpos], buf2, len2);
        in_wpos += len2;
    }

    in_widx += 1;
    if (in_widx >= CP_IN_FRAME_CNT) {
        in_widx -= CP_IN_FRAME_CNT;
    }
    cp_in_widx = in_widx;

    return 0;
}

CP_TEXT_SRAM_LOC
int a2dp_cp_get_in_frame(void **p_buf, uint32_t *p_len)
{
    uint16_t in_widx;
    uint16_t in_ridx;
    uint32_t in_rpos;

    if (reset_frames) {
        return -1;
    }

    in_widx = cp_in_widx;
    in_ridx = cp_in_ridx;

    if (in_widx == in_ridx) {
        return 1;
    }

    in_rpos = in_frames[in_ridx].pos;

    if (p_buf) {
        *p_buf = &cp_in_cache[in_rpos];
    }
    if (p_len) {
        *p_len = in_frames[in_ridx].len;
    }

    return 0;
}

CP_TEXT_SRAM_LOC
int a2dp_cp_consume_in_frame(void)
{
    uint16_t in_widx;
    uint16_t in_ridx;

    if (reset_frames) {
        return 0;
    }

    in_widx = cp_in_widx;
    in_ridx = cp_in_ridx;

    if (in_widx == in_ridx) {
        return 1;
    }

    in_ridx += 1;
    if (in_ridx >= CP_IN_FRAME_CNT) {
        in_ridx -= CP_IN_FRAME_CNT;
    }
    cp_in_ridx = in_ridx;

    return 0;
}

CP_TEXT_SRAM_LOC
uint32_t a2dp_cp_get_in_frame_index(void)
{
    return cp_in_ridx;
}

SRAM_TEXT_LOC
uint32_t a2dp_cp_get_in_frame_cnt_by_index(uint32_t ridx)
{
    return get_in_frame_cnt(cp_in_widx, ridx);
}

SRAM_TEXT_LOC
void a2dp_cp_reset_frame(void)
{
#ifdef A2DP_TRACE_CP_ACCEL
    TRACE_A2DP_DECODER_I("%s: Reset frames", __func__);
#endif

    reset_frames = true;

    // Notify CP to work again
    cp_accel_send_event_mcu2cp(1);
}

SRAM_TEXT_LOC
bool a2dp_cp_get_frame_reset_status(void)
{
    return reset_frames;
}

CP_TEXT_SRAM_LOC
enum CP_EMPTY_OUT_FRM_T a2dp_cp_get_emtpy_out_frame(void **p_buf, uint32_t *p_len)
{
    enum CP_EMPTY_OUT_FRM_T ret;
    uint8_t out_widx;
    uint32_t out_wpos;
    enum CP_DEC_STATE_T state ;

    if (reset_frames) {
        return CP_EMPTY_OUT_FRM_ERR;
    }

    out_widx = cp_out_widx;
    state = out_frames[out_widx].state;

    if (state != CP_DEC_STATE_IDLE && state != CP_DEC_STATE_WORKING) {
        return CP_EMPTY_OUT_FRM_ERR;
    }

    if (state == CP_DEC_STATE_WORKING) {
        ret = CP_EMPTY_OUT_FRM_WORKING;
    } else {
        out_frames[out_widx].state = CP_DEC_STATE_WORKING;
        ret = CP_EMPTY_OUT_FRM_OK;
    }

    out_wpos = out_frames[out_widx].pos;

    if (p_buf) {
        *p_buf = &cp_out_cache[out_wpos];
    }
    if (p_len) {
        *p_len = out_frames[out_widx].len;
    }

    return ret;
}

CP_TEXT_SRAM_LOC
int a2dp_cp_consume_emtpy_out_frame(void)
{
    uint8_t out_widx;

    if (reset_frames) {
        return 0;
    }

    out_widx = cp_out_widx;

    if (out_frames[out_widx].state != CP_DEC_STATE_WORKING) {
        return 1;
    }

    out_frames[out_widx].state = CP_DEC_STATE_DONE;

#ifdef A2DP_TRACE_CP_ACCEL
    TRACE_A2DP_DECODER_I("AD2P-CP out frame W[%d]", out_widx);
#endif

    out_widx += 1;
    if (out_widx >= CP_OUT_FRAME_CNT) {
        out_widx -= CP_OUT_FRAME_CNT;
    }
    cp_out_widx = out_widx;

    return 0;
}

SRAM_TEXT_LOC
int a2dp_cp_get_full_out_frame(void **p_buf, uint32_t *p_len)
{
    uint8_t out_ridx;
    uint32_t out_rpos;
    enum CP_DEC_STATE_T state ;

    if (reset_frames) {
        return -1;
    }

    out_ridx = cp_out_ridx;
    state = out_frames[out_ridx].state;

    if (state != CP_DEC_STATE_DONE && state != CP_DEC_STATE_INIT_DONE) {
        // Notify CP to work again
        cp_accel_send_event_mcu2cp(CP_BUILD_ID(CP_TASK_A2DP_DECODE, CP_EVENT_A2DP_DECODE));
        if (state == CP_DEC_STATE_WORKING){
            return  CP_EMPTY_OUT_FRM_WORKING;
        }else{
            return (10 +  state);
        }
    }

    out_rpos = out_frames[out_ridx].pos;

    if (state == CP_DEC_STATE_DONE) {
        if (p_buf) {
            *p_buf = &cp_out_cache[out_rpos];
        }
        if (p_len) {
            *p_len = out_frames[out_ridx].len;
        }
    } else {
        if (p_buf) {
            *p_buf = NULL;
        }
        if (p_len) {
            *p_len = 0;
        }
    }

    return 0;
}

SRAM_TEXT_LOC
int a2dp_cp_consume_full_out_frame(void)
{
    uint8_t out_ridx;
    enum CP_DEC_STATE_T state ;

    if (reset_frames) {
        return 0;
    }

    out_ridx = cp_out_ridx;
    state = out_frames[out_ridx].state;

    if (state != CP_DEC_STATE_DONE && state != CP_DEC_STATE_INIT_DONE) {
        return 1;
    }

#ifdef A2DP_TRACE_CP_ACCEL
    TRACE_A2DP_DECODER_I("AD2P-CP out frame R[%d]", out_ridx);
#endif

    out_frames[out_ridx].state = CP_DEC_STATE_IDLE;

    out_ridx += 1;
    if (out_ridx >= CP_OUT_FRAME_CNT) {
        out_ridx -= CP_OUT_FRAME_CNT;
    }
    cp_out_ridx = out_ridx;

    // Notify CP to work again
    cp_accel_send_event_mcu2cp(CP_BUILD_ID(CP_TASK_A2DP_DECODE, CP_EVENT_A2DP_DECODE));

    return 0;
}

#endif

