/*
 * Copyright (c) Hisilicon Technologies Co., Ltd. 2019-2019. All rights reserved.
 * Description: adec pts function
 * Author: audio
 * Create: 2019-12-30
 */

#include "adec_pts.h"
#include "mpi_adec_debug.h"
#include "adec_fault_check.h"

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

td_bool adec_is_pts_full(adec_pts_que *pts_que)
{
    td_u32 pts_free = adec_buf_free_size(pts_que->pts_write, pts_que->pts_read, ADEC_MAX_STORED_PTS_NUM);
    return (pts_free <= 1);
}

static td_u32 adec_traverse_pts_array(adec_pts_que *pts_que,
    td_u32 read_pos, td_u32 *found_pos, td_bool *pos_matched)
{
    td_u32 pos;
    td_u32 found_pts = ADEC_INVALID_PTS;
    adec_pts *pts_arry = pts_que->pts_arry;
    td_bool read_pos_flag = TD_FALSE;

    for (pos = pts_que->pts_read; pos != pts_que->pts_write; pos = (pos + 1) % ADEC_MAX_STORED_PTS_NUM) {
        if (pts_arry[pos].beg_pos < pts_arry[pos].end_pos) {
            if ((pts_arry[pos].beg_pos <= read_pos) && (pts_arry[pos].end_pos > read_pos)) {
                read_pos_flag = TD_TRUE;
                break;
            }
        } else {
            if ((pts_arry[pos].beg_pos <= read_pos) || (pts_arry[pos].end_pos > read_pos)) {
                read_pos_flag = TD_TRUE;
                break;
            }
        }
    }

    if (read_pos_flag == TD_TRUE) {
        found_pts = pts_arry[pos].pts;
        if (read_pos == pts_arry[pos].beg_pos) {
            *pos_matched = TD_TRUE;
        }
    }

    *found_pos = pos;

    return found_pts;
}

td_u32 adec_find_pts(adec_pts_find_info *param)
{
    td_u32 pos;
    td_u32 pts = ADEC_INVALID_PTS;
    td_u32 found_pts = ADEC_INVALID_PTS;
    td_u32 read_pos;
    td_u32 frame_time;
    adec_pts_que *pts_que = TD_NULL;
    adec_pts *pts_arry = TD_NULL;

    if (param == TD_NULL) {
        return 0;
    }

    read_pos = param->read_pos;
    frame_time = param->frame_time;
    pts_que = param->pts_que;
    if (pts_que == TD_NULL) {
        soc_log_err("pts_que is null!\n");
        return 0;
    }
    pts_arry = pts_que->pts_arry;

    param->out_pos_matched = TD_FALSE;
    param->out_pts_interpolated = TD_FALSE;
    param->out_org_pts = ADEC_INVALID_PTS;

    found_pts = adec_traverse_pts_array(pts_que, read_pos, &pos, &param->out_pos_matched);
    /* WARN: record first then pts_arry[pos].pts = ADEC_INVALID_PTS */
    if (pos != pts_que->pts_write) {
        adec_record_pts_processed(pts_que, pos, 1); /* read 1 pts && discard other */
    }

    if (found_pts == ADEC_INVALID_PTS) {
        /* can not find a valid PTS */
        param->out_org_pts = ADEC_INVALID_PTS;
        if (pts_que->last_pts_ms != ADEC_INVALID_PTS) {
            pts = pts_que->last_pts_ms + frame_time;
            if (pts == ADEC_INVALID_PTS) {
                pts = 0; /* avoid loop */
            }
            pts_que->last_pts_ms = pts;
            param->out_pts_interpolated = TD_TRUE;
        }
        pts_que->dfx.missed_pts_cnt++;
    } else {
        /* found a valid PTS */
        pts = found_pts;
        pts_arry[pos].pts = ADEC_INVALID_PTS;
        param->out_org_pts = found_pts;
        pts_que->last_pts_ms = pts;
    }

    if (pos != pts_que->pts_write) {
        pts_que->pts_read = pos;
    }

    return pts;
}

td_u32 adec_find_fix_pts(adec_pts_que *pts_que, td_u32 end_pos,
    td_u32 frame_time, td_u32 *org_pts)
{
    td_u32 pos;
    td_u32 pts = ADEC_INVALID_PTS;
    td_u32 found_pts_pos = ADEC_INVALID_PTS;
    adec_pts *pts_arry = pts_que->pts_arry;

    for (pos = pts_que->pts_read; pos != pts_que->pts_write; pos = (pos + 1) % ADEC_MAX_STORED_PTS_NUM) {
        if (pts_arry[pos].beg_pos < pts_arry[pos].end_pos) {
            if ((pts_arry[pos].beg_pos <= end_pos) && (pts_arry[pos].end_pos > end_pos)) {
                found_pts_pos = pts_arry[pos].pts;
                break;
            }
        } else {
            if ((pts_arry[pos].beg_pos <= end_pos) || (pts_arry[pos].end_pos > end_pos)) {
                found_pts_pos = pts_arry[pos].pts;
                break;
            }
        }
    }

    if (found_pts_pos == ADEC_INVALID_PTS) {
        /* can not find a valid PTS */
        if (pts_que->last_pts_ms != ADEC_INVALID_PTS) {
            pts = pts_que->last_pts_ms + frame_time;
            if (pts == ADEC_INVALID_PTS) {
                pts = 0; /* avoid loop */
            }
            pts_que->last_pts_ms = pts;
        }
        pts_que->dfx.missed_pts_cnt++;
        *org_pts = ADEC_INVALID_PTS;
    } else {
        /* found a valid PTS */
        adec_record_pts_processed(pts_que, pos, 1); /* read 1 pts && discard other */
        pts = found_pts_pos;
        pts_arry[pos].pts  = ADEC_INVALID_PTS;
        pts_que->pts_read = pos;
        pts_que->last_pts_ms = pts;
        *org_pts = found_pts_pos;
    }

    return pts;
}

td_void adec_discard_pts(adec_pts_que *pts_que, td_u32 read_pos)
{
    td_u32 pos;
    adec_pts *pts_arry = pts_que->pts_arry;

    for (pos = pts_que->pts_read; pos != pts_que->pts_write; pos = (pos + 1) % ADEC_MAX_STORED_PTS_NUM) {
        if (pts_arry[pos].beg_pos < pts_arry[pos].end_pos) {
            if ((pts_arry[pos].beg_pos <= read_pos) && (pts_arry[pos].end_pos > read_pos)) {
                break;
            }
        } else {
            if ((pts_arry[pos].beg_pos <= read_pos) || (pts_arry[pos].end_pos > read_pos)) {
                break;
            }
        }
    }

    adec_record_pts_processed(pts_que, pos, 0);
    pts_que->pts_read = pos;
}

td_void adec_store_pts(adec_pts_que *pts_que,
    const adec_chan_input_buf *in_buf, td_u32 pts, td_u32 size)
{
    adec_pts *pts_arry = pts_que->pts_arry;
    td_u32 tmp_pts = pts;

    if (tmp_pts != ADEC_INVALID_PTS) {
        if (tmp_pts == pts_que->last_store_pts_ms) {
            tmp_pts = ADEC_INVALID_PTS;
        }
    }

    if (tmp_pts == ADEC_INVALID_PTS) {
        td_u32 pts_pos = ADEC_MAX_STORED_PTS_NUM - 1;
        if (pts_que->pts_write != 0) {
            pts_pos = pts_que->pts_write - 1;
        }

        pts_arry[pts_pos].end_pos =
            adec_sat_add(pts_arry[pts_pos].end_pos, size, in_buf->boundary);
        return;
    }

    if (pts_que->dfx.first_valid_pts == ADEC_INVALID_PTS) {
        pts_que->dfx.first_valid_pts = tmp_pts;
    }
    /* make sure there are space to store */
    if (adec_is_pts_full(pts_que) == TD_TRUE) {
        pts_que->dfx.discard_pts_cnt++;
        soc_log_warn("not enough PTS buffer, discard current PTS(%d)\n", tmp_pts);
        return;
    }

    if (tmp_pts < pts_que->last_store_pts_ms) {
        if (pts_que->last_store_pts_ms == ADEC_INVALID_PTS) {
            pts_que->recyle_store_pts_ms = tmp_pts; /* first pts */
        } else {
            pts_que->recyle_store_pts_ms = pts_que->last_store_pts_ms; /* pts re-cycle */
        }

        pts_que->recycle_first_pts_ms = tmp_pts;
    }

    pts_que->last_store_pts_ms = tmp_pts;
    pts_arry[pts_que->pts_write].pts = tmp_pts;
    pts_arry[pts_que->pts_write].beg_pos = in_buf->stream_write_pos;
    pts_arry[pts_que->pts_write].end_pos =
        adec_sat_add(in_buf->stream_write_pos, size, in_buf->boundary);
    pts_que->pts_write = adec_sat_add(pts_que->pts_write, 1, ADEC_MAX_STORED_PTS_NUM);
}

#ifdef __cplusplus
}
#endif /* __cplusplus */