#include "lip_mac_tx.h"
#include "lip_mac_tx_cfg_defs.h"
#include "lip/ll/lip_ll.h"
#if LIP_MAC_TX_QUEUE_ENABLE
    #include "lip_mac_tx_queue.h"
#endif

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

#if !LIP_MAC_TX_QUEUE_ENABLE
static t_lip_tx_frame f_tx_frames[LIP_MAC_TX_FRAME_CNT]; /* информация о передаваемых кадрах */
static uint8_t f_tx_frames_put_pos;  /* позиция в f_tx_frames, которую будет сохранен следующий подготовленный кадр */
static uint8_t f_tx_frames_start_pos; /* позиция в f_tx_frames, по которой должен быть передан следующий кадр на передачу в порт */
static uint8_t f_tx_frames_done_pos;  /* позиция в f_tx_frames, за последним завершенным на передачу портом кдаром */
static uint8_t f_tx_frames_finish_pos; /* позиция в f_tx_frames, за последним постобработанным кадром */
/* Массив данных на передачу */
#define LSPEC_ALIGNMENT LIP_MAC_TX_FRAME_ALIGN
#include "lcspec_align.h"
LIP_MAC_TX_BUF_MEM(static uint8_t f_tx_frame_bufs[LIP_MAC_TX_BUF_SIZE]);
static uint16_t f_tx_indx[LIP_MAC_TX_FRAME_CNT];
static uint16_t f_tx_buf_put_pos;
static uint16_t f_tx_buf_done_pos;
#endif



void lip_max_tx_init(void) {
#if LIP_MAC_TX_QUEUE_ENABLE
    lip_max_tx_queue_reset();
#else
    f_tx_frames_put_pos = 0;
    f_tx_frames_start_pos = 0;
    f_tx_frames_done_pos = 0;
    f_tx_frames_finish_pos = 0;
    f_tx_buf_put_pos = f_tx_buf_done_pos = 0;
#endif
}

void lip_max_tx_close(void) {
#if LIP_MAC_TX_QUEUE_ENABLE
    lip_max_tx_queue_reset();
#endif
}

#if !LIP_MAC_TX_QUEUE_ENABLE
static inline uint8_t f_frame_tx_next_pos(uint8_t pos)  {
    uint8_t next_pos = pos + 1;
    if (next_pos >= LIP_MAC_TX_FRAME_CNT) {
        next_pos = 0;
    }
    return next_pos;
}
#endif

bool lip_mac_tx_rdy(void) {
#if LIP_MAC_TX_QUEUE_ENABLE
    return lip_mac_tx_queue_rdy();
#else
    uint32_t next_pos = f_frame_tx_next_pos(f_tx_frames_put_pos);
    return (next_pos != f_tx_frames_finish_pos) &&
          ((f_tx_frames_put_pos == f_tx_frames_finish_pos)
            || (f_tx_buf_put_pos > f_tx_buf_done_pos)
            || ((f_tx_buf_put_pos + LIP_MAC_TX_FRAME_MAX_SIZE) <= f_tx_buf_done_pos));
#endif
}



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

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

  Для отправки пакета после заполнения данных должна быть вызвана функция
  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_frame_prepare(const t_lip_tx_frame_info *info) {
#if LIP_MAC_TX_QUEUE_ENABLE
    return lip_mac_tx_queue_frame_prepare(info);
#else
    t_lip_tx_frame *tx_frame = NULL;
    if (lip_mac_tx_rdy()) {
        uint8_t *buf = &f_tx_frame_bufs[f_tx_buf_put_pos];

        tx_frame = &f_tx_frames[f_tx_frames_put_pos];

        tx_frame->info = *info;
        tx_frame->buf.start_ptr = tx_frame->buf.cur_ptr = buf;
        tx_frame->buf.end_ptr = tx_frame->buf.cur_ptr + LIP_MAC_TX_FRAME_MAX_SIZE;
        tx_frame->exdata_ptr = NULL;
        tx_frame->exdata_size = 0;
        tx_frame->status.flags = 0;
    }
    return tx_frame;
#endif
}

#if !LIP_MAC_TX_QUEUE_ENABLE
    static void f_try_start_tx_frames(void) {
        t_lip_errs err = LIP_ERR_SUCCESS;
        while ((f_tx_frames_put_pos != f_tx_frames_start_pos) && (err == LIP_ERR_SUCCESS)) {
            err = lip_ll_tx_frame(&f_tx_frames[f_tx_frames_start_pos]);
            if (err == LIP_ERR_SUCCESS) {
                f_tx_frames_start_pos = f_frame_tx_next_pos(f_tx_frames_start_pos);
            }
        }
        return ;
    }
#endif


/**************************************************************************//**
   Данная функция должна вызваться верхнем уровнем по завершению формирования
   пакета, место под который было получено с помощью 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_frame_flush(t_lip_tx_frame *frame) {
#if LIP_MAC_TX_QUEUE_ENABLE
    return lip_mac_tx_queue_frame_flush(frame);
#else
    uint16_t up_pos = frame->buf.cur_ptr - frame->buf.start_ptr;
    if (up_pos % LIP_MAC_TX_FRAME_ALIGN) {
        up_pos += (LIP_MAC_TX_FRAME_ALIGN - (up_pos % LIP_MAC_TX_FRAME_ALIGN));
    }
    f_tx_buf_put_pos += up_pos;
    if ((f_tx_buf_put_pos + LIP_MAC_TX_FRAME_MAX_SIZE) > LIP_MAC_TX_BUF_SIZE)
        f_tx_buf_put_pos = 0;
    f_tx_indx[f_tx_frames_put_pos] = f_tx_buf_put_pos;

    /* сохраняем позицию для дескриптора, чтобы рассчитать потом свободное место */
    f_tx_frames_put_pos = f_frame_tx_next_pos(f_tx_frames_put_pos);
    f_try_start_tx_frames();
return LIP_ERR_SUCCESS;
#endif
}

void lip_max_tx_frame_finish(t_lip_tx_frame *frame) {
#if LIP_PTP_ENABLE
    /* если метка переданного пакета уже доступна по завершению передачи
             * (TSTM выполняет локальный MAC), то сразу вызываем callback по
             * заверешнию обработки пакета.
             * В противном случае lip_ptp_proc_egress_tstmp() вызывается отдельно
             * уже после передачи пакета, когда метка будет доступна */
    if (frame->status.flags & LIP_FRAME_TXSTATUS_FLAG_TSTMP_VALID) {
        lip_ptp_proc_egress_tstmp(0, frame->info.ptp_msg_type, &frame->status.tstmp);
    }
#endif
    if (frame->info.flags & LIP_FRAME_TXINFO_FLAG_FINISH_CB) {
        frame->info.finish_cb(frame);
    }
}

/**************************************************************************//**
  Функция выполняет фоновую обработку очередей передачи данных.
  Выполняется проверка завершенных кадров, для которых овобождается место,
  занимаемое данными кадра и при необходимости вызывается пользовтельская
  функция обратного вызова.
 *****************************************************************************/
void lip_mac_tx_pull(void) {
#if LIP_MAC_TX_QUEUE_ENABLE
    lip_mac_tx_queue_pull();
#else
    f_try_start_tx_frames();
    uint8_t done_pos = f_tx_frames_done_pos;
    while (done_pos != f_tx_frames_finish_pos) {
        t_lip_tx_frame *frame = &f_tx_frames[f_tx_frames_finish_pos];
        lip_max_tx_frame_finish(frame);
        f_tx_buf_done_pos = f_tx_indx[f_tx_frames_finish_pos];
        f_tx_frames_finish_pos = f_frame_tx_next_pos(f_tx_frames_finish_pos);
    }
#endif
}

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

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

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

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

   @param[in] frame  Указатель на завершившийся кадр, должен соответствовать указателю,
                     переданному в lip_ll_tx_frame()
 ******************************************************************************/
void lip_mac_tx_frame_done_cb(t_lip_tx_frame *frame) {
#if LIP_MAC_TX_QUEUE_ENABLE
    lip_mac_tx_queue_frame_done(frame);
#else
    f_tx_frames_done_pos = f_frame_tx_next_pos(f_tx_frames_done_pos);
#endif
}
