#include "s600_can.h"
#include "s600_iap.h"
#include "string.h"
#include "stdlib.h"

#ifndef CONFIG_CAN_ID
#error CONFIG_CAN_ID not set
#endif

#define S600_CAN_LOOPBACK			0

static s600_can_packet_t s600_can_tx_packets[5];
static s600_can_packet_t *s600_can_tx_front;

static s600_can_packet_t s600_can_rx_packets[10];
static s600_can_packet_t *s600_can_rx_fronts[10];
static u8 s600_can_rx_front_len;

static u8 s600_can_tx_alloc_buff[NELEM(s600_can_tx_packets) + 1];
static byte_queue_t s600_can_tx_allocator;

static u8 s600_can_rx_alloc_buff[NELEM(s600_can_rx_packets) + 1];
static byte_queue_t s600_can_rx_allocator;

static u8 s600_can_tx_buff[NELEM(s600_can_tx_packets) + 1];
static byte_queue_t s600_can_tx_queue = { s600_can_tx_buff, NELEM(s600_can_tx_buff), 0, 0 };

static u8 s600_can_rx_buff[NELEM(s600_can_rx_packets) + 1];
static byte_queue_t s600_can_rx_queue = { s600_can_rx_buff, NELEM(s600_can_rx_buff), 0, 0 };

// ================================================================================

static u32 s600_can_write_position;

__STATIC_INLINE int s600_can_process_cmd_write_begin(const u8 *buff, u16 length)
{
	s600_can_write_position = 0;
	s600_iap_write_begin();
	return 0;
}

__STATIC_INLINE int s600_can_process_cmd_write_end(const u8 *buff, u16 length)
{
	u32 size, checksum;

	if (length < 7) {
		return -1;
	}

	size = s600_decode_u24(buff);
	checksum = s600_decode_u32(buff + 3);

	if (s600_iap_write_end(size, checksum)) {
		return 0;
	}

	return -1;
}

__STATIC_INLINE int s600_can_process_cmd_write_slow(u8 can, u8 *buff, u16 length)
{
	u32 position;

	if (length < 3) {
		return -1;
	}

	position = s600_decode_u24(buff);

	if (s600_can_write_position == position) {
		length -= 3;
		s600_iap_write(buff + 3, length);
		s600_can_write_position += length;
	}

	s600_encode_u24(buff + 1, s600_can_write_position);

	return 3;
}

__STATIC_INLINE void s600_can_process_command(u8 can, u8 *command, u16 length)
{
	int rsplen;
	u8 *args;
	u16 key;

	if (length < 2) {
		return;
	}

	key = *(u16 *) command;

	if ((key & 0xFFF0) != S600_CAN_BUILD_CMD(0xF0)) {
		return;
	}

	s600_iap_100ms = 0;
	args = command + 2;
	length -= 2;

	switch (key)
	{
	case S600_CAN_BUILD_CMD(0xF0):
		rsplen = 0;
		break;

	case S600_CAN_BUILD_CMD(0xF1):
		rsplen = s600_can_process_cmd_write_begin(args, length);
		break;

	case S600_CAN_BUILD_CMD(0xF2):
		rsplen = s600_can_process_cmd_write_end(args, length);
		break;

	case S600_CAN_BUILD_CMD(0xF3):
		s600_iap_write(args, length);
		return;

	case S600_CAN_BUILD_CMD(0xF4):
		rsplen = s600_can_process_cmd_write_slow(can, args, length);
		break;

	case S600_CAN_BUILD_CMD(0xF5):
		s600_iap_boot(false);
		rsplen = -1;
		break;

	default:
		rsplen = -1;
		break;
	}

	if (rsplen < 0) {
		rsplen = 0;
		args[0] = 0x01;
	} else {
		args[0] = 0x00;
	}

	s600_can_send_response(can, command, rsplen + 3);
}

// ================================================================================

__STATIC_INLINE void s600_can_rcu_config(void)
{
	byte_queue_alloc_init(&s600_can_tx_allocator, s600_can_tx_alloc_buff, sizeof(s600_can_tx_alloc_buff));
	byte_queue_alloc_init(&s600_can_rx_allocator, s600_can_rx_alloc_buff, sizeof(s600_can_rx_alloc_buff));

#if CONFIG_CAN_PIN_MAP == CAN_PIN_MAP_PA11_PA12
	rcu_periph_clock_enable(RCU_GPIOA);
#elif CONFIG_CAN_PIN_MAP == CAN_PIN_MAP_PB8_PB9
	rcu_periph_clock_enable(RCU_GPIOB);
#elif CONFIG_CAN_PIN_MAP == CAN_PIN_MAP_PD0_PD1
	rcu_periph_clock_enable(RCU_GPIOD);
#else
#error "Invalid CONFIG_CAN_PIN_MAP"
#endif

	rcu_periph_clock_enable(RCU_AF);
    rcu_periph_clock_enable(RCU_CAN0);
}

__STATIC_INLINE void s600_can_gpio_config(void)
{
#if CONFIG_CAN_PIN_MAP == CAN_PIN_MAP_PA11_PA12
	gpio_init(GPIOA, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_11);
	gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_12);
#elif CONFIG_CAN_PIN_MAP == CAN_PIN_MAP_PB8_PB9
	gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_8);
	gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
#ifdef GPIO_CAN0_PARTIAL_REMAP
	gpio_pin_remap_config(GPIO_CAN0_PARTIAL_REMAP, ENABLE);
#else
	gpio_pin_remap_config(GPIO_CAN_PARTIAL_REMAP, ENABLE);
#endif
#elif CONFIG_CAN_PIN_MAP == CAN_PIN_MAP_PD0_PD1
	gpio_init(GPIOD, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_0);
	gpio_init(GPIOD, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_1);
	gpio_pin_remap_config(GPIO_CAN_FULL_REMAP, ENABLE);
#else
#error "Invalid CONFIG_CAN_PIN_MAP"
#endif
}

void s600_can_device_config(void)
{
	can_parameter_struct can_parameter;
	can_filter_parameter_struct can_filter;

	s600_can_rcu_config();
	s600_can_gpio_config();

	// can_struct_para_init(CAN_INIT_STRUCT, &can_parameter);
	// can_struct_para_init(CAN_FILTER_STRUCT, &can_filter);

	can_deinit(CAN0);

	/* initialize CAN parameters */
	can_parameter.time_triggered = DISABLE;
	can_parameter.auto_bus_off_recovery = DISABLE;
	can_parameter.auto_wake_up = DISABLE;
	can_parameter.auto_retrans = DISABLE;
	can_parameter.rec_fifo_overwrite = DISABLE;
	can_parameter.trans_fifo_order = ENABLE;
	can_parameter.resync_jump_width = CAN_BT_SJW_1TQ;
	can_parameter.time_segment_1 = CAN_BT_BS1_5TQ;
	can_parameter.time_segment_2 = CAN_BT_BS2_3TQ;

#if S600_CAN_LOOPBACK
	can_parameter.working_mode = CAN_LOOPBACK_MODE;
#else
	can_parameter.working_mode = CAN_NORMAL_MODE;
#endif

#if S600_CAN_BAUDRATE == 1000
		can_parameter.prescaler = 6;
#elif S600_CAN_BAUDRATE == 500
		can_parameter.prescaler = 12;
#elif S600_CAN_BAUDRATE == 250
		can_parameter.prescaler = 24;
#elif S600_CAN_BAUDRATE == 125
		can_parameter.prescaler = 48;
#elif S600_CAN_BAUDRATE == 100
		can_parameter.prescaler = 60;
#elif S600_CAN_BAUDRATE == 50
		can_parameter.prescaler = 120;
#elif S600_CAN_BAUDRATE == 20
		can_parameter.prescaler = 300;
#else
	#error "Invalid Baudrate"
#endif

	/* initialize CAN */
	can_init(CAN0, &can_parameter);

#ifdef CONFIG_GD32E10X
	can_frequency_set(CAN0, S600_CAN_BAUDRATE * 1000);
#endif

	/* initialize filter */
	can_filter.filter_number = 0;
	can_filter.filter_mode = CAN_FILTERMODE_MASK;
	can_filter.filter_bits = CAN_FILTERBITS_32BIT;
	can_filter.filter_list_high = 0x0000;
	can_filter.filter_list_low = S600_CAN_DEST_ID(CONFIG_CAN_ID) << 3 | 1 << 2;
	can_filter.filter_mask_high = 0x0000;
	can_filter.filter_mask_low = S600_CAN_DEST_ID(S600_CAN_ALL_ID) << 3 | 1 << 2;
	can_filter.filter_fifo_number = CAN_FIFO1;
	can_filter.filter_enable = ENABLE;
	can_filter_init(&can_filter);

	nvic_irq_enable(CAN0_RX1_IRQn, 0, 0);
	can_interrupt_enable(CAN0, CAN_INTEN_RFNEIE1);
}

void s600_can_device_deinit(void)
{
	nvic_irq_disable(CAN0_RX1_IRQn);
	can_deinit(CAN0);
}

u8 s600_can_find_mailbox(void)
{
	u32 value = CAN_TSTAT(CAN0);

	if ((value & CAN_TSTAT_TME0) != 0) {
		return CAN_MAILBOX0;
	}

	if ((value & CAN_TSTAT_TME1) != 0) {
		return CAN_MAILBOX1;
	}

	if ((value & CAN_TSTAT_TME2) != 0) {
		return CAN_MAILBOX2;
	}

	return CAN_NOMAILBOX;
}

u8 s600_can_wait_mailbox(void)
{
	u32 count;

	for (count = 0; count < 1000000; count++) {
		u8 mailbox = s600_can_find_mailbox();

		if (mailbox != CAN_NOMAILBOX) {
			return mailbox;
		}
	}

	return CAN_NOMAILBOX;
}

void s600_can_send_frame(u8 mailbox, u32 efid, const u8 *buff, u8 length)
{
	/* set the data length */
	CAN_TMP(CAN0, mailbox) &= ~CAN_TMP_DLENC;
	CAN_TMP(CAN0, mailbox) |= length;

	/* set the data */
	CAN_TMDATA0(CAN0, mailbox) = ((const u32 *) buff)[0];

	if (length > 4) {
		CAN_TMDATA1(CAN0, mailbox) = ((const u32 *) buff)[1];
	}

	/* enable transmission */
	CAN_TMI(CAN0, mailbox) = TMI_EFID(efid) | CAN_FF_EXTENDED | CAN_FT_DATA | CAN_TMI_TEN;
}

bool s600_can_tx_packet(s600_can_packet_t *packet)
{
	u8 total, index;
	u8 mailbox;
	u16 length;
	u32 efid;

	mailbox = s600_can_find_mailbox();
	if (mailbox == CAN_NOMAILBOX) {
		return false;
	}

	total = (packet->length + 7) / 8;
	index = packet->offset / 8 + 1;

	efid = 6 << 26 | U32(packet->type) << 24
		| U32(total & 0x1F) << 19 | U32(index & 0x1F) << 14
		| S600_CAN_DEST_ID(packet->dest) | S600_CAN_SRC_ID(packet->src);

	length = packet->length - packet->offset;
	if (length > 8) {
		length = 8;
	}

	s600_can_send_frame(mailbox, efid, packet->data + packet->offset, length);
	packet->offset += length;

	return true;
}

__STATIC_INLINE s600_can_packet_t *s600_can_rx_front_get(u8 addr)
{
	u8 index;

	for (index = 0; index < s600_can_rx_front_len; index++) {
		s600_can_packet_t *packet = s600_can_rx_fronts[index];

		if (packet->src == addr) {
			return packet;
		}
	}

	if (s600_can_rx_front_len < NELEM(s600_can_rx_fronts)) {
		u8 index = byte_queue_alloc(&s600_can_rx_allocator);
		s600_can_packet_t *packet;

		if (index == 0xFF) {
			return NULL;
		}

		packet = s600_can_rx_packets + index;
		packet->src = addr;

#if S600_CAN_USE_MASK
		packet->mask = 0;
#else
		packet->index = 0xFF;
#endif

		s600_can_rx_fronts[s600_can_rx_front_len] = packet;
		s600_can_rx_front_len++;
		return packet;
	}

	return NULL;
}

__STATIC_INLINE bool s600_can_rx_front_put(s600_can_packet_t *packet)
{
	u8 index;

	for (index = 0; index < s600_can_rx_front_len; index++) {
		if (s600_can_rx_fronts[index] == packet) {
			s600_can_rx_front_len--;

			while (index < s600_can_rx_front_len) {
				s600_can_rx_fronts[index] = s600_can_rx_fronts[index + 1];
				index++;
			}

			return true;
		}
	}

	return false;
}

bool s600_can_rx_packet(u8 fifo)
{
	s600_can_packet_t *packet;
	u8 index, total;
	u32 *buff;
	u32 value;
	u32 efid;

	value = CAN_RFIFOMI(CAN0, fifo);
	if ((value & (CAN_RFIFOMI_FT | CAN_RFIFOMI_FF)) != (CAN_FT_DATA | CAN_FF_EXTENDED)) {
		return true;
	}

	efid = GET_RFIFOMI_EFID(value);

	value = CAN_RFIFOMP(CAN0, fifo);

#ifdef CAN_RFIFOMP_FDF
	if ((value & CAN_RFIFOMP_FDF) != CAN_FDF_CLASSIC) {
		return true;
	}
#endif

	packet = s600_can_rx_front_get((efid >> 7) & 0x7F);
	if (packet == NULL) {
		return false;
	}

	index = (efid >> 14) & 0x1F;
	total = (efid >> 19) & 0x1F;

#if S600_CAN_USE_MASK
	if (index == total) {
		packet->dest = efid & 0x7F;
		packet->type = (efid >> 24) & 0x03;
		packet->length = ((total - 1) & 0x1F) << 3;
		packet->length += GET_RFIFOMP_DLENC(value);
	}

	index = (index - 1) & 0x1F;

	if (index == 0) {
		packet->mask = 1;
	} else {
		packet->mask |= 1 << index;
	}

	buff = (u32 *) packet->datas[index];
	buff[0] = CAN_RFIFOMDATA0(CAN0, fifo);
	buff[1] = CAN_RFIFOMDATA1(CAN0, fifo);

	if (total == 0) {
		value = 0xFFFFFFFF;
	} else {
		value = (1 << total) - 1;
	}

	if ((packet->mask & value) == value) {
		byte_queue_write_byte(&s600_can_rx_queue, packet - s600_can_rx_packets);
		s600_can_rx_front_put(packet);
	}
#else
	if (index != packet->index) {
		if (index != 1) {
			return true;
		}

		packet->type = (efid >> 24) & 0x03;
		packet->dest = efid & 0x7F;
		packet->offset = 0;
		packet->index = 2;
	} else {
		packet->index = (index + 1) & 0x1F;
	}

	buff = (u32 *) (packet->data + packet->offset);
	packet->offset += GET_RFIFOMP_DLENC(value);
	buff[0] = CAN_RFIFOMDATA0(CAN0, fifo);
	buff[1] = CAN_RFIFOMDATA1(CAN0, fifo);

	if (total == index) {
		packet->length = packet->offset;
		byte_queue_write_byte(&s600_can_rx_queue, packet - s600_can_rx_packets);
		s600_can_rx_front_put(packet);
	}
#endif

	return true;
}

void s600_can_transmit(void)
{
	while (1) {
		u8 index;

		if (s600_can_tx_front) {
			if (s600_can_tx_front->offset < s600_can_tx_front->length) {
				s600_can_tx_packet(s600_can_tx_front);
				break;
			}

			byte_queue_free(&s600_can_tx_allocator, s600_can_tx_front - s600_can_tx_packets);
		}

		if (byte_queue_read(&s600_can_tx_queue, &index, 1) > 0) {
			s600_can_tx_front = s600_can_tx_packets + index;
		} else {
			s600_can_tx_front = NULL;
			break;
		}
	}
}

__STATIC_INLINE void s600_can_process_packet(s600_can_packet_t *packet)
{
	s600_can_process_command(packet->src, packet->data, packet->length);
}

void s600_can_poll(void)
{
	u8 index;

	s600_can_transmit();

	while (byte_queue_read(&s600_can_rx_queue, &index, 1) > 0) {
		s600_can_process_packet(s600_can_rx_packets + index);
		byte_queue_free(&s600_can_rx_allocator, index);
	}
}

bool s600_can_send_packet(u8 dest, u8 src, u8 type, const void *data, u16 length)
{
	s600_can_packet_t *packet;
	u8 index;

	index = byte_queue_alloc(&s600_can_tx_allocator);
	if (index == 0xFF) {
		return false;
	}

	packet = s600_can_tx_packets + index;

	packet->offset = 0;
	packet->type = type;
	packet->dest = dest;
	packet->src = src;
	packet->length = length;
	memcpy(packet->data, data, length);

	byte_queue_write_byte(&s600_can_tx_queue, index);
	s600_can_transmit();

	return true;
}

void s600_can_send_packet_block(u8 dest, u8 src, u8 type, const void *data, u16 length)
{
	while (!s600_can_send_packet(dest, src, type, data, length)) {
		s600_can_transmit();
	}
}

bool s600_can_send_command(u8 addr, const void *command, u16 length)
{
	return s600_can_send_packet(addr, CONFIG_CAN_ID, 1, command, length);
}

bool s600_can_send_response(u8 addr, const void *response, u16 length)
{
	return s600_can_send_packet(addr, CONFIG_CAN_ID, 2, response, length);
}

bool s600_can_send_event(u8 addr, const void *event, u16 length)
{
	return s600_can_send_packet(addr, CONFIG_CAN_ID, 3, event, length);
}

// ================================================================================

void CAN0_RX1_IRQHandler(void)
{
	s600_can_rx_packet(CAN_FIFO1);
	CAN_RFIFO1(CAN0) |= CAN_RFIFO1_RFD1;
}
