#include "lip_mac_rx.h"
#include "lip_mac_rx_queue.h"
#include "lip_mac_rx_cfg_defs.h"
#if LIP_MAC_RX_QUEUE_ENABLE
#include "lip/ll/lip_ll.h"

#include "lip/app/ptp/lip_ptp_cfg_defs.h"
#if LIP_SW_SUPPORT_KSZ8567
#include "lip/phy/ksz8567/lip_sw_ksz8567.h"
#endif
#if LIP_VLAN_ENABLE
#include "lip/link/lip_vlan.h"
#include "lip/link/lip_vlan_cfg_defs.h"
#endif

#include <string.h>


#define LSPEC_ALIGNMENT LIP_MAC_RX_FRAME_ALIGN


typedef uint16_t t_mac_rx_buf_pos;
typedef uint8_t  t_mac_rx_frame_pos;

/* описание состояния одной очереди на передачу пакетов */
typedef struct {
    uint8_t *buf; /* буфер очереди для хранения данных пакета */
    t_mac_rx_buf_pos buf_size; /* размер буфера в байтах */
    volatile t_mac_rx_buf_pos buf_put_pos; /* позиция в буфере, с которой будет сохранен следующий добавленный пакет */
    t_mac_rx_buf_pos buf_done_pos; /* позиция в буфере, соответствующая следующей за последним завершенным переданным байтом */
    t_lip_rx_frame *frames; /* массив с информацией о добавленных кадрах */
    t_mac_rx_frame_pos frames_max_cnt; /* макимальное число кадров в массиве frames */
    t_mac_rx_frame_pos frames_pos_msk; /* маска для определения значащих битов в позиции в массиве кадров для избежания переполнения */
    volatile t_mac_rx_frame_pos frame_put_pos;
    t_mac_rx_frame_pos frame_done_pos;
    uint32_t drop_frames_cnt;
} t_lip_mac_rx_queue_descr;

#include "lcspec_align.h"
LIP_MAC_RX_QUEUE0_BUF_MEM(static uint8_t f_rx_q0_buf[LIP_MAC_RX_QUEUE0_SIZE]);
#include "lcspec_align.h"
static t_lip_rx_frame f_rx_q0_frames[LIP_MAC_RX_QUEUE0_FRAME_CNT];
#if LIP_MAC_RX_QUEUE_CNT >= 2
#include "lcspec_align.h"
LIP_MAC_RX_QUEUE1_BUF_MEM(static uint8_t f_rx_q1_buf[LIP_MAC_RX_QUEUE1_SIZE]);
#include "lcspec_align.h"
static t_lip_rx_frame f_rx_q1_frames[LIP_MAC_RX_QUEUE1_FRAME_CNT];
#endif
#if LIP_MAC_RX_QUEUE_CNT >= 3
#include "lcspec_align.h"
LIP_MAC_RX_QUEUE2_BUF_MEM(static uint8_t f_rx_q2_buf[LIP_MAC_RX_QUEUE2_SIZE]);
#include "lcspec_align.h"
static t_lip_rx_frame f_rx_q2_frames[LIP_MAC_RX_QUEUE2_FRAME_CNT];
#endif
#if LIP_MAC_RX_QUEUE_CNT >= 4
#include "lcspec_align.h"
LIP_MAC_RX_QUEUE2_BUF_MEM(static uint8_t f_rx_q3_buf[LIP_MAC_RX_QUEUE3_SIZE]);
#include "lcspec_align.h"
static t_lip_rx_frame f_rx_q3_frames[LIP_MAC_RX_QUEUE3_FRAME_CNT];
#endif



static t_lip_mac_rx_queue_descr f_rx_q[LIP_MAC_RX_QUEUE_CNT] = {
    {f_rx_q0_buf, LIP_MAC_RX_QUEUE0_SIZE, 0, 0, f_rx_q0_frames, LIP_MAC_RX_QUEUE0_FRAME_CNT, LIP_MAC_RX_QUEUE0_FRAME_CNT-1, 0, 0, 0},
#if LIP_MAC_RX_QUEUE_CNT >= 2
    {f_rx_q1_buf, LIP_MAC_RX_QUEUE1_SIZE, 0, 0, f_rx_q1_frames, LIP_MAC_RX_QUEUE1_FRAME_CNT, LIP_MAC_RX_QUEUE1_FRAME_CNT-1, 0, 0, 0},
#endif
#if LIP_MAC_RX_QUEUE_CNT >= 3
    {f_rx_q2_buf, LIP_MAC_RX_QUEUE2_SIZE, 0, 0, f_rx_q2_frames, LIP_MAC_RX_QUEUE2_FRAME_CNT, LIP_MAC_RX_QUEUE2_FRAME_CNT-1, 0, 0, 0},
#endif
#if LIP_MAC_RX_QUEUE_CNT >= 4
    {f_rx_q3_buf, LIP_MAC_RX_QUEUE3_SIZE, 0, 0, f_rx_q3_frames, LIP_MAC_RX_QUEUE3_FRAME_CNT, LIP_MAC_RX_QUEUE3_FRAME_CNT-1, 0, 0, 0},
#endif
};

static inline t_mac_rx_frame_pos f_frame_pos_inc(const t_lip_mac_rx_queue_descr *q, t_mac_rx_frame_pos pos)  {
    return (pos + 1) & q->frames_pos_msk;
}

void lip_max_rx_queue_reset(void) {
    for (int q_idx = 0; q_idx < LIP_MAC_RX_QUEUE_CNT; ++q_idx) {
        t_lip_mac_rx_queue_descr *q = &f_rx_q[q_idx];
        q->buf_done_pos = q->buf_put_pos = 0;
        q->frame_put_pos = q->frame_done_pos = 0;
    }
}


/* Определение номера очереди для сохранения принятого пакета.
 * Если пакет PTP - то выбирается номер очереди из настроек LIP_PTP_RX_QUEUE_NUM
 * Если пакет c VLAN - то используется поле PCP из VLAN TCI и определяется номер очерди по настройке LIP_VLAN_RX_QUEUE_NUM
 * Иначе используется последняя (самая низкоприоритетная) очередь */
static uint8_t f_get_r_qidx(const t_lip_rx_ll_frame *ll_frame) {
#if LIP_PTP_ENABLE
    if (ll_frame->info.ll_flags & LIP_LL_RXFLAGS_PTP_PACKET) {
        return LIP_PTP_RX_QUEUE_NUM;
    }
#endif
#if LIP_VLAN_ENABLE
    if (ll_frame->info.flags & LIP_FRAME_RXINFO_FLAG_VLAN_PCP_VALID) {
        const uint8_t prio = ll_frame->info.vlan_pcp;
        return LIP_VLAN_RX_QUEUE_NUM(prio);
    }
#endif
    return LIP_MAC_RX_QUEUE_CNT - 1;
}

/***************************************************************************//**
 *  Функция вызывается портом каждый раз, когда им был принят новый действительный
 *  кадр данных.
 *  Функция может вызываться из контекста прерывания.
 *  Функция помещает переданный пакет и информацию о нем в одну из очередей для
 *  приема, копируя в нее все данные кадра, т.е. по завершению выволнения буфер
 *  порта может использоваться для приема новых кадров.
 *
 *  Перед вызовом порт заполняет всю информацию о кадре, полученную от аппаратуры,
 *  в поля информации о принятом кадре типа #t_lip_rx_frame_info, а также
 *  передает данные кадра.
 *  Данные могут быть разбиты на две части, для обработки случая, когда на уровне
 *  порта используется кольцевой буфер и данные кадра разбиты концом буфера на две
 *  части (в конце буфере и в начале).
 *
 *  В случае отсутствия места в требуемой очереди, кадр будет отброшен
 *
 *  @param[in] ll_frame - указатель на структуру с информацией о принятом кадре и
 *                        его данных.
 ******************************************************************************/


void lip_mac_rx_queue_proc_frame(t_lip_rx_ll_frame *ll_frame) {
    const uint8_t q_idx = f_get_r_qidx(ll_frame);
    t_lip_mac_rx_queue_descr *q = &f_rx_q[q_idx];

    /* проверяем, что есть место в очереди для помещения информации о кадре */
    const t_mac_rx_frame_pos frame_put_pos = q->frame_put_pos;
    const t_mac_rx_frame_pos frame_next_pos = f_frame_pos_inc(q, frame_put_pos);
    if (frame_next_pos != q->frame_done_pos) {
        /* проверка наличия места для данных кадра в буфере очереди */
        const t_mac_rx_buf_pos tsize = ll_frame->pl_main.size + ll_frame->pl_tail.size; /* общий размер складывается из основной и дополнительной части буфера */
        bool has_space = false;
        t_mac_rx_buf_pos buf_put_pos = q->buf_put_pos;
        t_mac_rx_buf_pos buf_done_pos = q->buf_done_pos;
        t_mac_rx_buf_pos buf_next_pos = buf_put_pos + tsize;
        /* определяем свободное место в буфере данных с учетом возможного перехода через конец буфера */
        t_mac_rx_buf_pos free_size = (buf_put_pos < buf_done_pos ? buf_done_pos - buf_put_pos :
                                          q->buf_size - (buf_put_pos - buf_done_pos));
        /* в буере данных очереди кадры уже должны хранится подряд без прехлеста для
         * удобной обработки, поэтому, если при добавлении кадра, он будет выходить
         * за конец буфере, то весь кадр помещается вначало.
         * При этом учитывается, что часть свободного места до конца буфера будет дополнительно
         * израсходована */
        if (buf_next_pos > q->buf_size) {
            has_space = free_size > (tsize + (q->buf_size - buf_put_pos));
            buf_put_pos = 0;
            buf_next_pos = tsize;

        } else {
            has_space = free_size > tsize;
        }

        if (has_space) {
            /* прямое копирование данных и информации о буфере, если есть место */
            t_lip_rx_frame *frame = &q->frames[frame_put_pos];
            frame->info = ll_frame->info;
            frame->pl.data = &q->buf[buf_put_pos];
            frame->pl.size = tsize;
            frame->fifo_num = q_idx;
            memcpy(frame->pl.data, ll_frame->pl_main.data, ll_frame->pl_main.size);
            if (ll_frame->pl_tail.size > 0) {
                memcpy(&frame->pl.data[ll_frame->pl_main.size], ll_frame->pl_tail.data, ll_frame->pl_tail.size);
            }
            q->buf_put_pos = buf_next_pos;
            q->frame_put_pos = frame_next_pos;
        } else {
            ++q->drop_frames_cnt;
        }
    } else {
        ++q->drop_frames_cnt;
    }
}

/***************************************************************************//**
  Функция выполняет просмотр всех очередей приема кадра в порядке приоритета
  и если обнаружен принятый кадр, возвращает стуруктуру с его описанием для
  обработки верхнем уровнем.

  После завершения обработки верхний уровень должен вызвать lip_mac_rx_get_frame_finish().

  @return Указатель на структуру с описанием принятого кадра или нулевой укаатель,
          если не обнаружено ни одного принятого кадра.
 ******************************************************************************/
t_lip_rx_frame *lip_mac_rx_queue_get_frame(void) {
    t_lip_rx_frame *frame = NULL;
    for (int q_idx = 0; (q_idx < LIP_MAC_RX_QUEUE_CNT) && !frame; ++q_idx) {
        t_lip_mac_rx_queue_descr *q = &f_rx_q[q_idx];
        if (q->frame_done_pos != q->frame_put_pos) {
            frame = &q->frames[q->frame_done_pos];
        }
    }
    return frame;
}


/***************************************************************************//**
  При вызове данной функции освобождается место, занимаемое указанным кадром.
  Функция должна вызываться по завершению обработки с указанием кадра, ранее
  полученного с помощью lip_mac_rx_get_frame().

  @param[in] Указатель на структуру с описанием кадра, обработка которого завершена.
 ******************************************************************************/
void lip_mac_rx_queue_get_frame_finish(t_lip_rx_frame *frame) {
    t_lip_mac_rx_queue_descr *q = &f_rx_q[frame->fifo_num];
    if (&q->frames[q->frame_done_pos] == frame) {
        lip_ll_irq_disable();
        q->frame_done_pos = f_frame_pos_inc(q, q->frame_done_pos);
        q->buf_done_pos = &frame->pl.data[frame->pl.size] - q->buf;
        lip_ll_irq_enable();
    }
}

/***************************************************************************//**
  Функция возвращает счетчик пакетов, которые были отброшены из-за отсутствия
  места в указанной очереди. Счетчик не сбрасываемый циклический.

  @param[in] Номер очереди, для которой будет возвращен счетчик.
 ******************************************************************************/
uint32_t lip_max_rx_queue_get_drop_frames_cnt(uint8_t q_idx) {
    return f_rx_q[q_idx].drop_frames_cnt;
}
#endif
