#include "lip_mac_tx_cfg_defs.h"
#if LIP_MAC_TX_QUEUE_ENABLE
#include "lip_mac_tx_queue.h"
#if LIP_PTP_ENABLE
#include "lip/app/ptp/lip_ptp_private.h"
#endif

typedef uint16_t t_mac_tx_buf_pos;
typedef uint8_t  t_mac_tx_frame_pos;

/* описание состояния одной очереди на передачу пакетов */
typedef struct {
    uint8_t *buf; /* буфер очереди для хранения данных пакета */
    t_mac_tx_buf_pos buf_size; /* размер буфера в байтах */
    t_mac_tx_buf_pos buf_put_pos; /* позиция в буфере, с которой будет сохранен следующий добавленный пакет */
    t_mac_tx_buf_pos buf_done_pos; /* позиция в буфере, соответствующая следующей за последним завершенным переданным байтом */
    t_lip_tx_frame *frames; /* массив с информацией о добавленных кадрах */
    t_mac_tx_frame_pos frames_pos_msk; /* маска для определения значащих битов в позиции в массиве кадров для избежания переполнения */
    t_mac_tx_frame_pos frame_put_pos;  /* позиция в массиве кадров для добавления следующего кадра */
    t_mac_tx_frame_pos frame_busy_cnt; /* число добавленных, но еще полностью не завершенных, кадров */
    t_mac_tx_frame_pos frame_finish_pos; /* позиция в массиве кадров за последним полностью обработанным кадром */
    volatile t_mac_tx_frame_pos frame_ll_start_pos;  /* позиция в массиве кадров кадра, который будет следующим добавлен для передачи нижним уровнем */
    volatile t_mac_tx_frame_pos frame_ll_done_pos;   /* позиция в массиве кадров за последним кадром, по которому была завершена передача нижнем уровнем */
} t_lip_mac_tx_queue_descr;


#define LSPEC_ALIGNMENT LIP_MAC_TX_FRAME_ALIGN

#include "lcspec_align.h"
LIP_MAC_TX_QUEUE0_BUF_MEM(static uint8_t f_tx_q0_buf[LIP_MAC_TX_QUEUE0_SIZE]);
#include "lcspec_align.h"
static t_lip_tx_frame f_tx_q0_frames[LIP_MAC_TX_QUEUE0_FRAME_CNT];
#include "lcspec_align.h"
#if LIP_MAC_TX_QUEUE_CNT >= 2
LIP_MAC_TX_QUEUE1_BUF_MEM(static uint8_t f_tx_q1_buf[LIP_MAC_TX_QUEUE1_SIZE]);
#include "lcspec_align.h"
static t_lip_tx_frame f_tx_q1_frames[LIP_MAC_TX_QUEUE1_FRAME_CNT];
#endif
#if LIP_MAC_TX_QUEUE_CNT >= 3
static uint8_t f_tx_q2_buf[LIP_MAC_TX_QUEUE2_SIZE];
#include "lcspec_align.h"
LIP_MAC_TX_QUEUE2_BUF_MEM(static t_lip_tx_frame f_tx_q2_frames[LIP_MAC_TX_QUEUE2_FRAME_CNT]);
#endif
#if LIP_MAC_TX_QUEUE_CNT >= 4
static uint8_t f_tx_q3_buf[LIP_MAC_TX_QUEUE3_SIZE];
#include "lcspec_align.h"
LIP_MAC_TX_QUEUE3_BUF_MEM(static t_lip_tx_frame f_tx_q3_frames[LIP_MAC_TX_QUEUE3_FRAME_CNT]);
#endif

static t_lip_mac_tx_queue_descr f_tx_q[LIP_MAC_TX_QUEUE_CNT] = {
    {f_tx_q0_buf, LIP_MAC_TX_QUEUE0_SIZE, 0, 0, f_tx_q0_frames, LIP_MAC_TX_QUEUE0_FRAME_CNT-1, 0, 0, 0, 0, 0},
#if LIP_MAC_TX_QUEUE_CNT >= 2
    {f_tx_q1_buf, LIP_MAC_TX_QUEUE1_SIZE, 0, 0, f_tx_q1_frames, LIP_MAC_TX_QUEUE1_FRAME_CNT-1, 0, 0, 0, 0, 0},
#endif
#if LIP_MAC_TX_QUEUE_CNT >= 3
    {f_tx_q2_buf, LIP_MAC_TX_QUEUE2_SIZE, 0, 0, f_tx_q2_frames, LIP_MAC_TX_QUEUE2_FRAME_CNT-1, 0, 0, 0, 0, 0},
#endif
#if LIP_MAC_TX_QUEUE_CNT >= 4
    {f_tx_q3_buf, LIP_MAC_TX_QUEUE3_SIZE, 0, 0, f_tx_q3_frames, LIP_MAC_TX_QUEUE3_FRAME_CNT-1, 0, 0, 0, 0, 0},
#endif
};

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

/* Выравниваем позицию на 4 байта для более быстрого доступа, а также проверяем, что доступно непрерывно
 * места на полный пакет. Если не доступно, то переходим к началу буфера */
static inline t_mac_tx_buf_pos f_frame_data_pos_align(const t_lip_mac_tx_queue_descr *q, t_mac_tx_buf_pos pos) {
    /* выравниваем размер для выравнивания начала следующего кадра */
    if (pos % LIP_MAC_TX_FRAME_ALIGN) {
        pos += (LIP_MAC_TX_FRAME_ALIGN - pos % LIP_MAC_TX_FRAME_ALIGN);
    }
    /* если до конца буфера остается меньше полного кадра, то начинаем заполнять
        буфер по кольцу сначала, чтобы не обрабатывать вариант "разорванного" буфера */
    if ((pos + LIP_MAC_TX_FRAME_MAX_SIZE) > q->buf_size) {
        pos = 0;
    }
    return pos;
}

static bool f_tx_queue_tx_rdy(const t_lip_mac_tx_queue_descr *q) {
    const t_mac_tx_frame_pos frame_busy_cnt = q->frame_busy_cnt;
    const t_mac_tx_buf_pos buf_done_pos = q->buf_done_pos;
    return (frame_busy_cnt < q->frames_pos_msk) /* сравниваем с pos_msk = cnt - 1, чтобы был один свободный элемент в очереди,
                                                   т.к. в дольнейшем равенство различны frame pos считается пустой очередью */
           && ((frame_busy_cnt == 0)
               || (q->buf_put_pos > buf_done_pos)
               || ((q->buf_put_pos + LIP_MAC_TX_FRAME_MAX_SIZE) <= buf_done_pos));
}

/* проверка, что ни в одной очереди нет готовых на передачу пакетов */
static bool f_no_rdy_pkts(void) {
    bool ret = true;
    for (int q_idx = 0; (q_idx < LIP_MAC_TX_QUEUE_CNT) && ret; ++q_idx) {
        const t_lip_mac_tx_queue_descr *q = &f_tx_q[q_idx];
        const t_mac_tx_frame_pos done_pos = q->frame_ll_done_pos;
        const t_mac_tx_frame_pos put_pos = q->frame_put_pos;
        if (put_pos != done_pos) {
            ret = false;
        }
    }
    return ret;
}

void lip_max_tx_queue_reset(void) {
    for (int q_idx = 0; q_idx < LIP_MAC_TX_QUEUE_CNT; ++q_idx) {
        t_lip_mac_tx_queue_descr *q = &f_tx_q[q_idx];
        q->buf_done_pos = q->buf_put_pos = 0;
        q->frame_put_pos = q->frame_finish_pos = q->frame_ll_start_pos = q->frame_ll_done_pos = 0;
        q->frame_busy_cnt = 0;
    }
}


bool lip_mac_tx_queue_rdy(void) {
    return f_tx_queue_tx_rdy(&f_tx_q[0]);
}


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

  Если место найдено, то функция заполняет информацию о кадра, включая указатель
  на буфер для формирования данных кадра, и возвращает ее для дальнейшей обработки.

  Для отправки пакета после заполнения данных должна быть вызвана функция
  lip_mac_tx_frame_flush(). Если lip_mac_tx_frame_prepare() вызван повторно до
  lip_mac_tx_frame_flush(), то результаты предыдущего выдления становятся
  недействительны и пакет может быть  выделен на том же месте.

  Если место не нейдено, то будет возвращаен нулевой указатель.

  @param[in] info  Указатель на структуру с информацией о кадре, для формирования
                   которого выделяется буфер.
  @return          Указатель с описанием выделенного кадра или нулевой указатель
                   при отсутствии места.
 *****************************************************************************/
t_lip_tx_frame *lip_mac_tx_queue_frame_prepare(const t_lip_tx_frame_info *info) {
    t_lip_tx_frame *frame = NULL;
    const int q_idx = LIP_MAC_TX_QUEUE_NUM(info->priority);
    const t_lip_mac_tx_queue_descr *q = &f_tx_q[q_idx];
    /* если в очереди есть место для нового кадра размером в LIP_MAC_TX_FRAME_MAX_SIZE,
     * то возвращаем описание кадра */
    if (f_tx_queue_tx_rdy(q)) {
        frame = &q->frames[q->frame_put_pos];
        frame->info = *info;
        frame->buf.start_ptr = frame->buf.cur_ptr = &q->buf[q->buf_put_pos];
        frame->buf.end_ptr = frame->buf.cur_ptr + LIP_MAC_TX_FRAME_MAX_SIZE;
        frame->exdata_ptr = NULL;
        frame->exdata_size = 0;
        frame->status.flags = 0;
    }
    return frame;
}


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

   Вызов lip_mac_tx_frame_flush() всегда должен соответствовать посленему вызову
   lip_mac_tx_frame_prepare() и указатель на кадр должен быть передан тот же,
   что получен от lip_mac_tx_frame_prepare().

   После вызова это функции данные и информацию о кадре изменять нельзя. Он помечается
   как готовый к отправке и может быть немедленно передан портом в сеть, если
   порт не занят передачей более приоритетных данных.

   @param[in] frame  Указатель с описанием сформированного кадра. Должен соответствовать
                     полученному при последнем вызыве lip_mac_tx_frame_prepare().
   @return           Код ошибки.
 *****************************************************************************/
t_lip_errs lip_mac_tx_queue_frame_flush(t_lip_tx_frame *frame) {
    t_lip_errs err = LIP_ERR_SUCCESS;
    const int q_idx = LIP_MAC_TX_QUEUE_NUM(frame->info.priority);
    t_lip_mac_tx_queue_descr *q = &f_tx_q[q_idx];
    /* по текущей позиции заполнения буфера пониманием, в какой части полезные данные,
     * которые нужно передать и откуда начинать следующий кадр */
    t_mac_tx_buf_pos buf_size = frame->buf.cur_ptr - frame->buf.start_ptr;
    q->buf_put_pos = f_frame_data_pos_align(q, q->buf_put_pos + buf_size);
    ++q->frame_busy_cnt;
    /* запрет прерываний перед изменением frame_put_pos и попыткой передать
     * кадр, чтобы не было вероятности двойной отсылки кадра при попадании прерывания
     * на момент с обновления frame_put_pos до завершения посылки */
    lip_ll_irq_disable();
    /* пытаемся передавать добавленный кадр сразу для самой приоритетной очереди,
     * для остальных очередей, только если нет больше пакетов ожидающих передачи.
       f_no_rdy_pkts проверяется до изменения frame_put_pos, чтобы не учитывать
       только что добавленный пакет.
       Также проверяем, что ll_start соответствует пакету, т.е. уже в очереди нет
       пакетов, которых не удалось добавить на передачу */
    t_mac_tx_frame_pos ll_start = q->frame_ll_start_pos;

    bool tx_req = (q->frame_put_pos == ll_start) && ((q_idx == 0) || f_no_rdy_pkts());
    q->frame_put_pos = f_frame_pos_inc(q, q->frame_put_pos);
    if (tx_req) {
        if (lip_ll_tx_frame(frame) == LIP_ERR_SUCCESS) {
            q->frame_ll_start_pos = f_frame_pos_inc(q, ll_start);
        }
    }
    lip_ll_irq_enable();
    return err;
}

/**************************************************************************//**
  Функция выполняет фоновую обработку очередей передачи данных.
  Выполняется проверка завершенных кадров, для которых овобождается место,
  занимаемое данными кадра и при необходимости вызывается пользовтельская
  функция обратного вызова.
 *****************************************************************************/
void lip_mac_tx_queue_pull(void) {
    /* обрабатываем пакеты, по которым была завершена передача,
     * вызывая при необходимости пользовательский callback (из контекста
     * основной программы, а не прерывания) */
    for (int q_idx = 0; q_idx < LIP_MAC_TX_QUEUE_CNT; ++q_idx) {
        t_lip_mac_tx_queue_descr *q = &f_tx_q[q_idx];
        t_mac_tx_frame_pos tx_done_pos = q->frame_ll_done_pos;
        while (q->frame_finish_pos != tx_done_pos) {
            t_lip_tx_frame *frame = &q->frames[q->frame_finish_pos];
            /* done_pos выравниваем точно так же, как put_pos, с переходом вначало.
             * иначе есть вероятность, что done_pos будет меньше чем в полном пакете до
             * конца и put_pos до него не дойдет, и перекинется снова вначала, потеряв
             * весь буфер */
            q->buf_done_pos = f_frame_data_pos_align(q, frame->buf.cur_ptr - q->buf);
            q->frame_finish_pos = f_frame_pos_inc(q, q->frame_finish_pos);
            --q->frame_busy_cnt;
            lip_max_tx_frame_finish(frame);
        }
    }
}

/**************************************************************************//**
  Функция может вызвваться в контексте прерывания.

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

  Перед завершением порт должен обновить статус передачи кадра.

  После вызова данные кадра считаются больше не нужными и это место может использоваться
  для формирования новых кадров после обработки в lip_mac_tx_pull().

   @param[in] frame  Указатель на завершившийся кадр, должен соответствовать указателю,
                     переданному в lip_ll_tx_frame()
 ******************************************************************************/
void lip_mac_tx_queue_frame_done(t_lip_tx_frame *frame) {
    /* обработка завершения передачи пакета низким уровнем (возможен вызов из контекста
     * прерывания) */
    t_lip_mac_tx_queue_descr *q_finish = &f_tx_q[LIP_MAC_TX_QUEUE_NUM(frame->info.priority)];
    /* обновляем позицию заверешнных пакетов */
    q_finish->frame_ll_done_pos = f_frame_pos_inc(q_finish, q_finish->frame_ll_done_pos);
    bool out = false;
    /* пробуем запустить следующий пакет из самой приоритетной очереди */
    for (int q_idx = 0; (q_idx < LIP_MAC_TX_QUEUE_CNT) && !out; ++q_idx) {
        t_lip_mac_tx_queue_descr *q = &f_tx_q[q_idx];
        if (q->frame_ll_done_pos !=  q->frame_put_pos) {
            /* выходим из проверки по первой очереди, в которой есть незавершенные пакеты,
             * чтобы не планировать новые более низкоприоритетные до заврешения выскоприроритетных */
            out = true;
            t_mac_tx_frame_pos ll_start = q->frame_ll_start_pos;
            if (q->frame_put_pos != ll_start) {
                if (lip_ll_tx_frame(&q->frames[ll_start]) == LIP_ERR_SUCCESS) {
                    q->frame_ll_start_pos = f_frame_pos_inc(q, ll_start);
                }
            }
        }
    }
}
#endif
