/*
 * j1939.c
 *
 *  Created on: May 3, 2024
 *      Author: colorbass
 */

#include "main.h"
#include "j1939.h"
#include "charger_gbt.h"
#include "string.h"
#include "can.h"
#include "edcan.h"
#include "debug.h"
#include <string.h>

extern GBT_BCL_t GBT_ReqPower;
extern GBT_BCL_t GBT_CurrPower;

j_receive_t j_rx;

typedef struct{
	uint8_t data[256];
	uint32_t PGN;
	uint16_t size;
	uint8_t packets;
	uint8_t next_packet;
	uint8_t step;
	uint8_t state; // 0=idle, 1=wait CTS, 2=wait ACK
	uint32_t tick;
}j_transmit_t;

static j_transmit_t j_tx;

#define J1939_BUFFER_SIZE 64U

typedef struct {
	j1939_rx_frame_t buffer[J1939_BUFFER_SIZE];
	uint16_t head;
	uint16_t tail;
	uint16_t count;
	uint32_t overflow;
} j1939_rx_ring_t;

typedef struct {
	j1939_tx_frame_t buffer[J1939_BUFFER_SIZE];
	uint16_t head;
	uint16_t tail;
	uint16_t count;
	uint8_t busy;
	uint32_t overflow;
} j1939_tx_ring_t;

static j1939_rx_ring_t j_rxbuf;
static j1939_tx_ring_t j_txbuf;
static uint32_t j_tx_watchdog_last_log_tick;

#define J1939_CRITICAL_ENTER() __disable_irq()
#define J1939_CRITICAL_EXIT() __enable_irq()

static void J_SendTpRts(void);
static void J_SendTpDtRange(uint8_t start_packet, uint8_t count);
static void J_TxCheckTimeout(void);
static void J_TxReset(void);
static void J1939_ProcessRxFrame(const j1939_rx_frame_t *frame);
static void J1939_RxBufferAdd(const j1939_rx_frame_t *frame);
static uint8_t J1939_RxBufferGet(j1939_rx_frame_t *frame);
static void J1939_TxBufferAdd(const j1939_tx_frame_t *frame);
static uint8_t J1939_TxBufferPeek(j1939_tx_frame_t *frame);
static void J1939_TxBufferDropFirst(void);
static void J1939_TxQueueByPgn(uint32_t PGN, uint8_t pri, uint8_t DLC, const uint8_t *data, uint8_t pad_ff);

#define J1939_TP_CTS_WAIT_TIMEOUT_MS 1000U
#define J1939_TP_ACK_WAIT_TIMEOUT_MS 1000U

static void J_TxReset(void){
	if(j_tx.state != 0){
		log_printf(LOG_WARN, "J1939 chunk send failed: TX session reset (state=%u pgn=0x%lX next=%u/%u)\n",
				j_tx.state, j_tx.PGN, j_tx.next_packet, j_tx.packets);
	}
	memset(&j_tx, 0, sizeof(j_tx));
}

void J1939_InitBuffers(void){
	memset(&j_rxbuf, 0, sizeof(j_rxbuf));
	memset(&j_txbuf, 0, sizeof(j_txbuf));
	j_tx_watchdog_last_log_tick = 0U;
	J_TxReset();
}

uint16_t J1939_GetRxBufferCount(void){
	uint16_t c;
	J1939_CRITICAL_ENTER();
	c = j_rxbuf.count;
	J1939_CRITICAL_EXIT();
	return c;
}

uint16_t J1939_GetTxBufferCount(void){
	uint16_t c;
	J1939_CRITICAL_ENTER();
	c = j_txbuf.count;
	J1939_CRITICAL_EXIT();
	return c;
}

uint32_t J1939_GetRxOverflowCount(void){
	return j_rxbuf.overflow;
}

uint32_t J1939_GetTxOverflowCount(void){
	return j_txbuf.overflow;
}

void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan){
	j1939_rx_frame_t frame;
	CAN_RxHeaderTypeDef rx_header;
	uint8_t rx_data[8] = {0};

	if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rx_header, rx_data) != HAL_OK) return;

	frame.ExtId = rx_header.ExtId;
	frame.DLC = rx_header.DLC;
	frame.tick = HAL_GetTick();
	memcpy(frame.data, rx_data, sizeof(frame.data));
	J1939_RxBufferAdd(&frame);
}

static void J1939_ProcessRxFrame(const j1939_rx_frame_t *frame){
	uint32_t pgn;

	if((frame->ExtId & 0x00FFFF) != ((J_ID_EV << 8) | J_ID_SE)) return;  // SA, DA match

	switch ((frame->ExtId >> 8) & 0x00FF00){
	case 0xEC00:
		if(frame->data[0] == 16){ // RTS
			j_rx.size = frame->data[1] | (frame->data[2] << 8);
			j_rx.packet = 1;
			j_rx.packets = frame->data[3];
			j_rx.step = 2;
			j_rx.step_cts_remain = j_rx.step;
			j_rx.PGN = (frame->data[7] << 16) | (frame->data[6] << 8) | frame->data[5];
			if(j_rx.size < 256){
				J_SendCTS(j_rx);
				j_rx.state = 1;
			}
		}
		if(frame->data[0] == 17){ // CTS
			pgn = (frame->data[7] << 16) | (frame->data[6] << 8) | frame->data[5];
			if((j_tx.state == 1) && (pgn == j_tx.PGN)){
				uint8_t req_count = frame->data[1];
				uint8_t req_start_packet = frame->data[2];
				log_printf(LOG_DEBUG, "J1939 event: TP CTS received (pgn=0x%lX cnt=%u start=%u exp=%u)\n",
						pgn, req_count, req_start_packet, j_tx.next_packet);
				if(req_count == 0){
					log_printf(LOG_DEBUG, "J1939 event: TP CTS wait (pgn=0x%lX)\n", j_tx.PGN);
				}else{
					j_tx.tick = frame->tick;
					J_SendTpDtRange(req_start_packet, req_count);
				}
			}else{
				log_printf(LOG_WARN, "J1939 chunk send failed: CTS ignored (tx_state=%u tx_pgn=0x%lX rx_pgn=0x%lX)\n",
						j_tx.state, j_tx.PGN, pgn);
			}
		}
		if(frame->data[0] == 19){ // ACK
			pgn = (frame->data[7] << 16) | (frame->data[6] << 8) | frame->data[5];
			if((j_tx.state == 2) && (pgn == j_tx.PGN)){
				log_printf(LOG_DEBUG, "J1939 event: TP ACK received, TX complete (pgn=0x%lX)\n", pgn);
				j_tx.state = 0;
			}
		}
		if(frame->data[0] == 255){
			j_rx.state = 0;
			J_TxReset();
		}
		break;

	case 0xEB00:
		if(j_rx.state != 1) return;
		if((frame->data[0] > 0) && (frame->data[0] < 35) && (j_rx.packet == frame->data[0])){
			memcpy(&j_rx.data[(frame->data[0]-1)*7], &frame->data[1], 7);
			j_rx.packet++;
			if(j_rx.packet > j_rx.packets){
				J_SendACK(j_rx);
				j_rx.state = 2;
			}else{
				if(j_rx.step_cts_remain > 0) j_rx.step_cts_remain--;
				if(j_rx.step_cts_remain == 0){
					J_SendCTS(j_rx);
					j_rx.step_cts_remain = 2;
				}
			}
		}
		break;

	case 0x1E00:
		log_printf(LOG_WARN, "J1939 fault received: BEM, forcing stop (data=%02X %02X %02X %02X %02X %02X %02X %02X)\n",
				frame->data[0], frame->data[1], frame->data[2], frame->data[3],
				frame->data[4], frame->data[5], frame->data[6], frame->data[7]);
		GBT_ForceStop();
		break;

	case 0x1900:
		/* Repeated BST frames during STOP flow are normal on the bus.
		 * Handle/log only once to avoid flooding UART logs.
		 */
		if((GBT_State != GBT_STOP) && (GBT_State != GBT_STOP_CSD) && (GBT_State != GBT_COMPLETE)){
			log_printf(LOG_DEBUG, "J1939 event: BST received, initiating stop flow (data=%02X %02X %02X %02X %02X %02X %02X %02X)\n",
					frame->data[0], frame->data[1], frame->data[2], frame->data[3],
					frame->data[4], frame->data[5], frame->data[6], frame->data[7]);
			GBT_StopEV(GBT_CST_BMS_ACTIVELY_SUSPENDS);
		}
		break;

	default:{
		uint32_t short_pgn = (frame->ExtId >> 8) & 0x00FF00U;
		/* Однокадровые PGN обрабатываем сразу; j_rx зарезервирован только под TP RX. */
		if((short_pgn != 0xEC00U) && (short_pgn != 0xEB00U)){
			GBT_ApplyShortPacket(short_pgn, frame->data, frame->DLC);
		}
		break;
	}
	}
}

void J1939_ExchangeRxBuffer(void){
	j1939_rx_frame_t frame;
	J_TxCheckTimeout();
	if(J1939_RxBufferGet(&frame)){
		J1939_ProcessRxFrame(&frame);
	}
}

void J1939_ExchangeTxBuffer(void){
	j1939_tx_frame_t frame;
	CAN_TxHeaderTypeDef tx_header;
	uint32_t tx_mailbox;
	HAL_StatusTypeDef tx_status;

	if(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0) return;

	/* Atomically reserve first queued frame without holding IRQ over HAL call */
	J1939_CRITICAL_ENTER();
	if(j_txbuf.busy || (j_txbuf.count == 0U)){
		J1939_CRITICAL_EXIT();
		return;
	}
	j_txbuf.busy = 1U;
	frame = j_txbuf.buffer[j_txbuf.tail];
	J1939_CRITICAL_EXIT();

	tx_header.ExtId = frame.ExtId;
	tx_header.RTR = CAN_RTR_DATA;
	tx_header.IDE = CAN_ID_EXT;
	tx_header.DLC = frame.DLC;

	tx_status = HAL_CAN_AddTxMessage(&hcan1, &tx_header, frame.data, &tx_mailbox);

	J1939_CRITICAL_ENTER();
	if((tx_status == HAL_OK) && (j_txbuf.count > 0U)){
		j_txbuf.tail = (j_txbuf.tail + 1U) % J1939_BUFFER_SIZE;
		j_txbuf.count--;
	}
	j_txbuf.busy = 0U;
	J1939_CRITICAL_EXIT();

	if(tx_status != HAL_OK){
		log_printf(LOG_WARN, "J1939 send failed: CAN TX error (st=%d extid=0x%lX dlc=%u free_mb=%lu err=0x%lX)\n",
				(int)tx_status, frame.ExtId, frame.DLC, HAL_CAN_GetTxMailboxesFreeLevel(&hcan1), HAL_CAN_GetError(&hcan1));
		if((tx_status == HAL_ERROR) && (HAL_CAN_GetError(&hcan1) & HAL_CAN_ERROR_NOT_INITIALIZED)){
			log_printf(LOG_WARN, "J1939 send failed: CAN not initialized, reinitializing\n");
			GBT_CAN_ReInit();
		}
	}
}

void GBT_CAN_ReInit(){
	HAL_CAN_Stop(&hcan1);
	MX_CAN1_Init();
	HAL_CAN_Start(&hcan1);
	HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
	GBT_CAN_FilterInit();
	J1939_InitBuffers();
}

void J_SendPacket(uint32_t PGN, uint8_t pri, uint8_t DLC, uint8_t *data){
        J_TxCheckTimeout();

        if(DLC > 8){
        	/* Transport protocol for multi-packet payloads */
        	if(DLC > sizeof(j_tx.data)) return;
        	if(j_tx.state != 0){
        		log_printf(LOG_WARN, "J1939 chunk send failed: TX busy (state=%u req_pgn=0x%lX cur_pgn=0x%lX)\n",
        				j_tx.state, PGN, j_tx.PGN);
        		return;
        	}

        	memset(&j_tx, 0, sizeof(j_tx));
        	memcpy(j_tx.data, data, DLC);
        	j_tx.PGN = PGN;
        	j_tx.size = DLC;
        	j_tx.packets = (DLC + 6) / 7;
        	j_tx.next_packet = 1;
        	j_tx.step = 2;
        	j_tx.state = 1;
        	j_tx.tick = HAL_GetTick();
        	log_printf(LOG_DEBUG, "J1939 event: TP RTS sent (pgn=0x%lX size=%u packets=%u)\n",
        			j_tx.PGN, j_tx.size, j_tx.packets);

        	J_SendTpRts();
        	return;
        }
        J1939_TxQueueByPgn(PGN, pri, DLC, data, 1);

}

static void J_TxCheckTimeout(void){
	if(j_tx.state != 0){
		uint32_t now = HAL_GetTick();
		if((now - j_tx_watchdog_last_log_tick) >= 500U){
			log_printf(LOG_DEBUG, "J1939 event: TP TX watchdog (state=%u pgn=0x%lX next=%u/%u age=%lu)\n",
					j_tx.state, j_tx.PGN, j_tx.next_packet, j_tx.packets, (now - j_tx.tick));
			j_tx_watchdog_last_log_tick = now;
		}
	}else{
		j_tx_watchdog_last_log_tick = HAL_GetTick();
	}

	if((j_tx.state == 1) && ((HAL_GetTick() - j_tx.tick) > J1939_TP_CTS_WAIT_TIMEOUT_MS)){
		log_printf(LOG_WARN, "J1939 chunk send failed: CTS timeout (pgn=0x%lX), resetting TX session\n", j_tx.PGN);
		J_TxReset();
	}else if((j_tx.state == 2) && ((HAL_GetTick() - j_tx.tick) > J1939_TP_ACK_WAIT_TIMEOUT_MS)){
		log_printf(LOG_WARN, "J1939 chunk send failed: ACK timeout (pgn=0x%lX), resetting TX session\n", j_tx.PGN);
		J_TxReset();
	}
}

static void J_SendTpRts(void){
	uint8_t data[8];

	data[0] = 16; // CONTROL_BYTE_TP_CM_RTS
	data[1] = j_tx.size & 0xFF;
	data[2] = j_tx.size >> 8;
	data[3] = j_tx.packets;
	data[4] = j_tx.step; // max packets before next CTS
	data[5] = j_tx.PGN;
	data[6] = j_tx.PGN >> 8;
	data[7] = j_tx.PGN >> 16;

	J1939_TxQueueByPgn(0x00EC00, 7, 8, data, 0);
}

static void J_SendTpDtRange(uint8_t start_packet, uint8_t count){
	uint8_t dt[8];
	uint8_t packet;
	uint8_t i;
	uint16_t idx;

	if(j_tx.state != 1){
		log_printf(LOG_WARN, "J1939 chunk send failed: TP DT ignored (invalid TX state=%u)\n", j_tx.state);
		return;
	}

	if((start_packet == 0) || (start_packet > j_tx.packets)) return;
	if(count == 0) return;
	if(start_packet != j_tx.next_packet){
		log_printf(LOG_WARN, "J1939 chunk send failed: TP desync (start=%u expected=%u), resetting TX session\n", start_packet, j_tx.next_packet);
		J_TxReset();
		return;
	}
	if((start_packet + count - 1) > j_tx.packets){
		count = j_tx.packets - start_packet + 1;
	}

	for(packet = 0; packet < count; packet++){
		uint8_t seq = start_packet + packet;
		dt[0] = seq;
		idx = (uint16_t)(seq - 1) * 7;
		for(i = 0; i < 7; i++){
			if((idx + i) < j_tx.size) dt[i + 1] = j_tx.data[idx + i];
			else dt[i + 1] = 0xFF;
		}
		J1939_TxQueueByPgn(0x00EB00, 7, 8, dt, 0);
	}
	log_printf(LOG_DEBUG, "J1939 event: TP DT sent (pgn=0x%lX start=%u cnt=%u)\n", j_tx.PGN, start_packet, count);

	j_tx.next_packet = start_packet + count;
	j_tx.tick = HAL_GetTick();
	if(j_tx.next_packet > j_tx.packets){
		j_tx.state = 2; // wait ACK
	}else{
		j_tx.state = 1; // wait next CTS
	}
}

//void J_SendPacketLong(){
//	//TODO (no need)
//}

// J1939 sequence Clear To Send packet
void J_SendCTS(j_receive_t rx){

	//if(rx.packets <= rx.packet) return; TODO
    uint8_t data[8];
	data[0] = 17; //CONTROL_BYTE_TP_CM_CTS
	data[1] = rx.step;//total_number_of_packages_transmitted
	if (rx.step > (rx.packets - rx.packet+1)) data[1] = rx.packets - rx.packet+1;
	data[2] = rx.packet;//next_packet_number_transmitted
	data[3] = 0xFF; /* Reserved */
	data[4] = 0xFF;
	data[5] = rx.PGN;
	data[6] = rx.PGN >> 8;
	data[7] = rx.PGN >> 16;

	J_SendPacket(0x00EC00, 7, 8, data);
}

// J1939 sequence ACK packet
void J_SendACK(j_receive_t rx){//uint32_t PGN, uint8_t step, uint8_t packet){

    uint8_t data[8];
	data[0] = 19; //CONTROL_BYTE_TP_CM_ACK
	data[1] = j_rx.size;
	data[2] = j_rx.size>>8;
	data[3] = j_rx.packets;
	data[4] = 0xFF;//TODO
	data[5] = rx.PGN;
	data[6] = rx.PGN >> 8;
	data[7] = rx.PGN >> 16;

	J_SendPacket(0x00EC00, 7, 8, data);
}

void GBT_CAN_FilterInit(){
	  CAN_FilterTypeDef  sFilterConfig;

	  sFilterConfig.FilterBank = 0;
	  sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
	  sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
	  sFilterConfig.FilterIdHigh = 0x0000;
	  sFilterConfig.FilterIdLow = 0x0000;
	  sFilterConfig.FilterMaskIdHigh = 0x0000;
	  sFilterConfig.FilterMaskIdLow = 0x0000;
	  sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
	  sFilterConfig.FilterActivation = ENABLE;
	  //sFilterConfig.SlaveStartFilterBank = 14;
	  if(HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
	  {
	    Error_Handler();
	  }

}

static void J1939_RxBufferAdd(const j1939_rx_frame_t *frame){
	J1939_CRITICAL_ENTER();
	j_rxbuf.buffer[j_rxbuf.head] = *frame;
	j_rxbuf.head = (j_rxbuf.head + 1U) % J1939_BUFFER_SIZE;
	if(j_rxbuf.count == J1939_BUFFER_SIZE){
		j_rxbuf.tail = (j_rxbuf.tail + 1U) % J1939_BUFFER_SIZE;
		j_rxbuf.overflow++;
		log_printf(LOG_WARN, "J1939 RX buffer overflow: dropped oldest frame, overflow_count=%lu\n", j_rxbuf.overflow);
	}else{
		j_rxbuf.count++;
	}
	J1939_CRITICAL_EXIT();
}

static uint8_t J1939_RxBufferGet(j1939_rx_frame_t *frame){
	uint8_t ok = 0;
	J1939_CRITICAL_ENTER();
	if(j_rxbuf.count > 0){
		*frame = j_rxbuf.buffer[j_rxbuf.tail];
		j_rxbuf.tail = (j_rxbuf.tail + 1U) % J1939_BUFFER_SIZE;
		j_rxbuf.count--;
		ok = 1;
	}
	J1939_CRITICAL_EXIT();
	return ok;
}

static void J1939_TxBufferAdd(const j1939_tx_frame_t *frame){
	J1939_CRITICAL_ENTER();
	j_txbuf.buffer[j_txbuf.head] = *frame;
	j_txbuf.head = (j_txbuf.head + 1U) % J1939_BUFFER_SIZE;
	if(j_txbuf.count == J1939_BUFFER_SIZE){
		j_txbuf.tail = (j_txbuf.tail + 1U) % J1939_BUFFER_SIZE;
		j_txbuf.overflow++;
		log_printf(LOG_WARN, "J1939 TX buffer overflow: dropped oldest frame, overflow_count=%lu\n", j_txbuf.overflow);
	}else{
		j_txbuf.count++;
	}
	J1939_CRITICAL_EXIT();
}

static uint8_t J1939_TxBufferPeek(j1939_tx_frame_t *frame){
	uint8_t ok = 0;
	J1939_CRITICAL_ENTER();
	if(j_txbuf.count > 0){
		*frame = j_txbuf.buffer[j_txbuf.tail];
		ok = 1;
	}
	J1939_CRITICAL_EXIT();
	return ok;
}

static void J1939_TxBufferDropFirst(void){
	J1939_CRITICAL_ENTER();
	if(j_txbuf.count > 0){
		j_txbuf.tail = (j_txbuf.tail + 1U) % J1939_BUFFER_SIZE;
		j_txbuf.count--;
	}
	J1939_CRITICAL_EXIT();
}

static void J1939_TxQueueByPgn(uint32_t PGN, uint8_t pri, uint8_t DLC, const uint8_t *data, uint8_t pad_ff){
	j1939_tx_frame_t frame;
	uint8_t i;

	frame.ExtId = (pri << 26) | (PGN << 8) | (J_ID_SE << 8) | J_ID_EV;
	frame.DLC = DLC;
	for(i = 0; i < 8; i++){
		if(i < DLC) frame.data[i] = data[i];
		else frame.data[i] = pad_ff ? 0xFF : 0x00;
	}
	J1939_TxBufferAdd(&frame);
}
