#include "ets_sys.h"
#include "osapi.h"
#include "gpio.h"
#include "os_type.h"
#include "user_interface.h"
#include "module.h"
#include "lauxlib.h"
#include "task/task.h"
#include "platform.h"
#include <stdlib.h>
#include <string.h>

#define SOFTUART_MAX_RX_BUFF 128
#define SOFTUART_GPIO_COUNT 13

//TODO: Overflow flag as callback function + docs

typedef struct {
	char receive_buffer[SOFTUART_MAX_RX_BUFF];
	uint8_t buffer_first;
	uint8_t buffer_last;
	uint8_t bytes_count;
	uint8_t buffer_overflow;
} softuart_buffer_t;

typedef struct {
	volatile softuart_buffer_t buffer;
	uint16_t bit_time;
	uint16_t need_len; // Buffer length needed to run callback function
	char end_char; // Used to run callback if last char in buffer will be the same
	uint8_t armed;
	uint8_t pin_rx;
	uint8_t pin_tx;
} softuart_t;

// Array of pointers to SoftUART instances
softuart_t * softuart_gpio_instances[SOFTUART_GPIO_COUNT] = {NULL};
// Array of callback reference to be able to find which callback is used to which rx pin
static int softuart_rx_cb_ref[SOFTUART_GPIO_COUNT];
// Task for receiving data
static task_handle_t uart_recieve_task = 0;
// Receiving buffer for callback usage
static char softuart_rx_buffer[SOFTUART_MAX_RX_BUFF];

static inline int32_t asm_ccount(void) {
    int32_t r;
    asm volatile ("rsr %0, ccount" : "=r"(r));
    return r;
}

static inline uint8_t checkbit(uint8_t data, uint8_t bit)
{
	if ((data & bit) != 0) {
		return 1;
	} else {
		return 0;
	}
}

static uint32_t ICACHE_RAM_ATTR softuart_intr_handler(uint32_t ret_gpio_status)
{
	// Disable all interrupts
	ets_intr_lock();
	int32_t start_time = asm_ccount();
	uint32_t gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
	uint32_t gpio_bits = gpio_status;
	for (uint8_t gpio_bit = 0; gpio_bits != 0; gpio_bit++, gpio_bits >>= 1) {
		// Check all pins for interrupts
		if (! (gpio_bits & 0x01)) continue;
		// We got pin that was interrupted
		// Load instance which has rx pin on interrupt pin attached
		softuart_t *s = softuart_gpio_instances[pin_num_inv[gpio_bit]];
		if (s == NULL) continue;
		// Clear interrupt status on that pin
		GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & (1 << pin_num[s->pin_rx]));
		ret_gpio_status &= ~(1 << pin_num[s->pin_rx]);
		if (softuart_rx_cb_ref[pin_num_inv[gpio_bit]] == LUA_NOREF) continue;
		if (!s->armed) continue;
		// There is an armed SoftUART rx instance on that pin
		// Start listening to transmission
		// TODO: inverted
		if (! (GPIO_INPUT_GET(GPIO_ID_PIN(pin_num[s->pin_rx])))) {
		//pin is low - therefore we have a start bit
			unsigned byte = 0;
			// Casting and using signed types to always be able to compute elapsed time even if there is a overflow
			uint32_t elapsed_time = (uint32_t)(asm_ccount() - start_time);

			// Wait till start bit is half over so we can sample the next one in the center
			if (elapsed_time < s->bit_time / 2) {
				uint16_t wait_time = s->bit_time / 2 - elapsed_time;
				while ((uint32_t)(asm_ccount() - start_time) < wait_time);
				start_time += wait_time;
			}

			// Sample bits
			// TODO: How many bits? Add other configs to softuart
			for (uint8_t i = 0; i < 8; i ++ ) {
				while ((uint32_t)(asm_ccount() - start_time) < s->bit_time);
				//shift d to the right
				byte >>= 1;

				// Read bit
				if(GPIO_INPUT_GET(GPIO_ID_PIN(pin_num[s->pin_rx]))) {
					// If high, set MSB of byte to 1
					byte |= 0x80;
				}
				// Recalculate start time for next bit
				start_time += s->bit_time;
			}

			// Store byte in buffer
			// If buffer full, set the overflow flag and return
			if (s->buffer.bytes_count == SOFTUART_MAX_RX_BUFF) {
				s->buffer.buffer_overflow = 1;
			} else if (s->buffer.bytes_count < SOFTUART_MAX_RX_BUFF) {
				s->buffer.receive_buffer[s->buffer.buffer_last] = byte;
				s->buffer.buffer_last++;
				s->buffer.bytes_count++;

				// Check for callback conditions
				if (((s->need_len != 0) && (s->buffer.bytes_count >= s->need_len))  || \
						((s->need_len == 0) && ((char)byte == s->end_char))) {
					    // Send the pointer to task handler
						s->armed = 0;
						task_post_medium(uart_recieve_task, (task_param_t)s);
				}
			}
			// Check for overflow after appending new byte
			if (s->buffer.bytes_count == SOFTUART_MAX_RX_BUFF) {
				s->buffer.buffer_overflow = 1;
			}
			// Roll over buffer index if necessary
			if (s->buffer.buffer_last == SOFTUART_MAX_RX_BUFF) {
				s->buffer.buffer_last = 0;
			}
			// Wait for stop bit
			// TODO: Add config for stop bits and parity bits
			while ((uint32_t)(asm_ccount() - start_time) < s->bit_time);
			// Break the loop after reading of the frame
			break;
		}
	}
	// re-enable all interrupts
	ets_intr_unlock();
	return ret_gpio_status;
}

static void softuart_putchar(softuart_t *s, char data)
{
	// Disable all interrupts
	ets_intr_lock();
	int32_t start_time = asm_ccount();
	// Set start bit
	GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[s->pin_tx]), 0);
	for (uint32_t i = 0; i < 8; i++) {
		// Wait to transmit another bit
		while ((uint32_t)(asm_ccount() - start_time) < s->bit_time);

		GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[s->pin_tx]), checkbit(data, 1 << i));
		// Recalculate start time for next bit
		start_time += s->bit_time;
	}

	// Stop bit
	while ((uint32_t)(asm_ccount() - start_time) < s->bit_time);
	GPIO_OUTPUT_SET(GPIO_ID_PIN(pin_num[s->pin_tx]), 1);
	// Delay after byte, for new sync
	os_delay_us(s->bit_time*6 / system_get_cpu_freq());
	// Re-enable all interrupts
	ets_intr_unlock();
}

static int softuart_init(softuart_t *s)
{

	// Init tx pin
    if (s->pin_tx != 0xFF) {
        platform_gpio_mode(s->pin_tx, PLATFORM_GPIO_OUTPUT, PLATFORM_GPIO_PULLUP);
        platform_gpio_write(s->pin_tx, PLATFORM_GPIO_HIGH);
    }

    // Init rx pin
    if (s->pin_rx != 0xFF) {
		platform_gpio_mode(s->pin_rx, PLATFORM_GPIO_INT, PLATFORM_GPIO_PULLUP);
		// Enable interrupt for pin on falling edge
		platform_gpio_intr_init(s->pin_rx, GPIO_PIN_INTR_NEGEDGE);
		softuart_gpio_instances[s->pin_rx] = s;
		// Preserve other rx gpio pins
		uint32_t mask = 0;
		for (uint8_t i = 0; i < SOFTUART_GPIO_COUNT; i++) {
			if (softuart_gpio_instances[i] != NULL) {
				mask = mask | (1 << pin_num[softuart_gpio_instances[i]->pin_rx]);
			}
		}
		return platform_gpio_register_intr_hook(mask, softuart_intr_handler);
    }
    return 1;
}


static int softuart_setup(lua_State *L)
{
	uint32_t baudrate;
	uint8_t tx_gpio_id, rx_gpio_id;
	softuart_t *softuart = NULL;

	NODE_DBG("[SoftUART]: setup called\n");
	baudrate = (uint32_t)luaL_checkinteger(L, 1); // Get Baudrate from
	luaL_argcheck(L, (baudrate > 0 && baudrate < 230400), 1, "Invalid baud rate");

  if (lua_isnoneornil(L, 2)) {
    tx_gpio_id = 0xFF;
  } else {
    tx_gpio_id = (uint8_t)luaL_checkinteger(L, 2);
    luaL_argcheck(L, (platform_gpio_exists(tx_gpio_id) && tx_gpio_id != 0)
      , 2, "Invalid SoftUART tx GPIO");
  }

  if (lua_isnoneornil(L, 3)) {
    rx_gpio_id = 0xFF;
  } else {
    rx_gpio_id = (uint8_t)luaL_checkinteger(L, 3);
		luaL_argcheck(L, (platform_gpio_exists(rx_gpio_id) && rx_gpio_id != 0)
			, 3, "Invalid SoftUART rx GPIO");
		luaL_argcheck(L, softuart_gpio_instances[rx_gpio_id] == NULL
			, 3, "SoftUART rx already configured on the pin");
  }

  // SoftUART object without receive and transmit part would be useless
  if ((rx_gpio_id == 0xFF) && (tx_gpio_id == 0xFF)) {return luaL_error(L, "Not enough arguments");}

	softuart = (softuart_t*)lua_newuserdata(L, sizeof(softuart_t));
	softuart->pin_rx = rx_gpio_id;
	softuart->pin_tx = tx_gpio_id;
	softuart->need_len = SOFTUART_MAX_RX_BUFF;
	softuart->armed = 0;

	// Set buffer
	softuart->buffer.buffer_first = 0;
	softuart->buffer.buffer_last = 0;
	softuart->buffer.bytes_count = 0;
	softuart->buffer.buffer_overflow = 0;

	// Set bit time
    softuart->bit_time = system_get_cpu_freq() * 1000000 / baudrate;

    // Set metatable
	luaL_getmetatable(L, "softuart.port");
	lua_setmetatable(L, -2);
	// Init SoftUART
	int result = softuart_init(softuart);
	if (result == 0) {
		luaL_error(L, "Couldn't register interrupt");
	}
	return 1;
}

static void softuart_rx_callback(task_param_t arg)
{
	softuart_t *softuart = (softuart_t*)arg; //Receive pointer from ISR
	lua_State *L = lua_getstate();
	lua_rawgeti(L, LUA_REGISTRYINDEX, softuart_rx_cb_ref[softuart->pin_rx]);

	// Clear overflow flag if needed
	if(softuart->buffer.bytes_count == SOFTUART_MAX_RX_BUFF) {
		softuart->buffer.buffer_overflow = 0;
	}
	// Copy volatile data to static buffer
	uint8_t buffer_length = softuart->buffer.bytes_count;
	for (int i = 0; i < buffer_length; i++) {
		softuart_rx_buffer[i] = softuart->buffer.receive_buffer[softuart->buffer.buffer_first];
		softuart->buffer.buffer_first++;
		softuart->buffer.bytes_count--;
		if (softuart->buffer.buffer_first == SOFTUART_MAX_RX_BUFF) {
			softuart->buffer.buffer_first = 0;
		}
	}
	lua_pushlstring(L, softuart_rx_buffer, buffer_length);
	softuart->armed = 1;
	luaL_pcallx(L, 1, 0);
}


// Arguments: event name, minimum buffer filled to run callback, callback function
static int softuart_on(lua_State *L)
{
	NODE_DBG("[SoftUART] on: called\n");
	size_t name_len, arg_len;

	softuart_t *softuart = (softuart_t*)luaL_checkudata(L, 1, "softuart.port");
	const char *method = luaL_checklstring(L, 2, &name_len);
	luaL_argcheck(L, lua_isfunction(L, 4), -1, "No callback function specified");
	luaL_argcheck(L, (name_len == 4 && strcmp(method, "data") == 0), 2, "Method not supported");
    luaL_argcheck(L, softuart->pin_rx != 0xFF, 1, "Rx pin was not declared");

	if (lua_isnumber(L, 3)) {
		luaL_argcheck(L, luaL_checkinteger(L, 3) < SOFTUART_MAX_RX_BUFF,
				2, "Argument bigger than SoftUART buffer");
		softuart->end_char = 0;
		softuart->need_len = (uint16_t) luaL_checkinteger(L, 3);
	} else if (lua_isstring(L, 3)) {
		const char *end = luaL_checklstring(L , 3,  &arg_len);
		luaL_argcheck(L, arg_len == 1, 3, "Wrong end char length");
		softuart->end_char = end[0];
		softuart->need_len = 0;
	} else {
		return luaL_error(L, "Wrong argument type");
	}

    lua_settop(L, 4); // Move to the top of the stack
    // Register callback or reregister new one
    luaL_reref(L, LUA_REGISTRYINDEX, &softuart_rx_cb_ref[softuart->pin_rx]);
    // Arm the instance
	softuart->armed = 1;
    return 0;
}

static int softuart_write(lua_State *L)
{
	softuart_t *softuart = NULL;
	size_t str_len;
	softuart = (softuart_t*) luaL_checkudata(L, 1, "softuart.port");
	luaL_argcheck(L, softuart->pin_tx != 0xFF, 1, "Tx pin was not declared");

	if (lua_isnumber(L, 2)) {
		// Send byte
		uint32_t byte = (uint32_t)luaL_checkinteger(L, 2);
		luaL_argcheck(L, byte < 256, 2, "Integer too large for a byte");
		softuart_putchar(softuart, (char)byte);
	} else if (lua_isstring(L, 2)) {
		// Send string
		const char *string = luaL_checklstring(L, 2, &str_len);
		for (size_t i = 0; i < str_len; i++) {
			softuart_putchar(softuart, string[i]);
		}
	} else {
		return luaL_error(L, "Wrong argument type");
    }
	return 0;
}

static int softuart_gcdelete(lua_State *L)
{
	NODE_DBG("SoftUART GC called\n");
	softuart_t *softuart = NULL;
	softuart = (softuart_t*) luaL_checkudata(L, 1, "softuart.port");
	uint8_t last_instance = 1;
	for(uint8_t instance = 0; instance < SOFTUART_GPIO_COUNT; instance++)
		if (softuart_gpio_instances[instance] != NULL && instance != softuart->pin_rx)
			last_instance = 0;

	softuart_gpio_instances[softuart->pin_rx] = NULL;
	luaL_unref2(L, LUA_REGISTRYINDEX, softuart_rx_cb_ref[softuart->pin_rx]);
	// Try to unregister the interrupt hook if this was last or the only instance
	if (last_instance)
		platform_gpio_register_intr_hook(0, softuart_intr_handler);
	return 0;
}

// Port function map
LROT_BEGIN(softuart_port, NULL, LROT_MASK_GC_INDEX)
	LROT_FUNCENTRY( __gc, softuart_gcdelete)
	LROT_TABENTRY( __index, softuart_port)
	LROT_FUNCENTRY( on, softuart_on)
	LROT_FUNCENTRY( write, softuart_write)
LROT_END(softuart_port, NULL, LROT_MASK_GC_INDEX)

// Module function map
LROT_BEGIN(softuart, LROT_TABLEREF(softuart_port), 0)
	LROT_FUNCENTRY( setup, softuart_setup)
LROT_END(softuart, LROT_TABLEREF(softuart_port), 0)

static int luaopen_softuart(lua_State *L)
{
	for(int i = 0; i < SOFTUART_GPIO_COUNT; i++) {
		softuart_rx_cb_ref[i] = LUA_NOREF;
	}
	uart_recieve_task = task_get_id((task_callback_t) softuart_rx_callback);
	luaL_rometatable(L, "softuart.port", LROT_TABLEREF(softuart_port));
	return 0;
}

NODEMCU_MODULE(SOFTUART, "softuart", softuart, luaopen_softuart);
