#include "serial_port_lua.h"
#include "types.h"
#include "error.h"

extern "C"
{
	#include <lua.h>
	#include <lauxlib.h>
	#include <lualib.h>
}

using MicroMacro::SerialPort;

const char *LuaType::metatable_serial_port = "serial_port";

int Serial_port_lua::regmod(lua_State *L)
{
	const luaL_Reg meta[] = {
		{"__gc", Serial_port_lua::gc},
		{"__tostring", Serial_port_lua::tostring},
		{NULL, NULL}
	};

	const luaL_Reg methods[] = {
		{"isConnected", Serial_port_lua::isConnected},
		{"close", Serial_port_lua::close},
		{"read", Serial_port_lua::read},
		{"write", Serial_port_lua::write},
		{NULL, NULL}
	};


	luaL_newmetatable(L, LuaType::metatable_serial_port);
	luaL_setfuncs(L, meta, 0);
	luaL_newlib(L, methods);
	lua_setfield(L, -2, "__index");

	lua_pop(L, 1); // Pop table

	return MicroMacro::ERR_OK;
}

int Serial_port_lua::gc(lua_State *L)
{
	int top = lua_gettop(L);
	if( top != 1 )
		wrongArgs(L);
	checkType(L, LT_USERDATA, 1);

	SerialPort *pSerialPort = *static_cast<SerialPort **>(lua_touserdata(L, 1));

	pSerialPort->close();

	// Can now delete new'd memory
	delete pSerialPort;
	return 0;
}

int Serial_port_lua::tostring(lua_State *L)
{
	int top = lua_gettop(L);
	if( top != 1 )
		wrongArgs(L);
	checkType(L, LT_USERDATA, 1);

	SerialPort *pSerialPort = *static_cast<SerialPort **>(lua_touserdata(L, 1));

	std::string portName;
	if( pSerialPort->connected )
	{
		portName = "Serial Port ";
		portName += pSerialPort->portName;
	}
	else
	{
		portName = "Disconnected Serial Port";
	}

	lua_pushstring(L, portName.c_str());
	return 1;
}

int Serial_port_lua::cleanup(lua_State *L)
{
	return MicroMacro::ERR_OK;
}

int Serial_port_lua::isConnected(lua_State *L)
{
	int top = lua_gettop(L);
	if( top != 1 )
		wrongArgs(L);
	checkType(L, LT_USERDATA, 1);

	SerialPort *pSerialPort = *static_cast<SerialPort **>(lua_touserdata(L, 1));

	lua_pushboolean(L, pSerialPort->connected);
	return 1;
}

int Serial_port_lua::close(lua_State *L)
{
	int top = lua_gettop(L);
	if( top != 1 )
		wrongArgs(L);
	checkType(L, LT_USERDATA, 1);

	SerialPort *pSerialPort = *static_cast<SerialPort **>(lua_touserdata(L, 1));

	pSerialPort->close();
	return 0;
}

int Serial_port_lua::read(lua_State *L)
{
	int top = lua_gettop(L);
	if( top != 1 && top != 2 )
		wrongArgs(L);
	checkType(L, LT_USERDATA, 1);

	SerialPort *pSerialPort = *static_cast<SerialPort **>(lua_touserdata(L, 1));

	DWORD maxRead = SERIAL_BUFFER_SIZE;
	char buffer[SERIAL_BUFFER_SIZE];
	unsigned int toRead = 0;
	DWORD bytesRead;
	DWORD errors;
	ClearCommError(pSerialPort->handle, &errors, &pSerialPort->status);
	bool moreQueued	=	false;


	// If the user requested to read a specific amount of data, take care of that here
	if( top >= 2 )
	{
		checkType(L, LT_NUMBER, 2);
		maxRead = lua_tointeger(L, 2);
		if( maxRead > SERIAL_BUFFER_SIZE )
			maxRead = SERIAL_BUFFER_SIZE;
	}

	if( pSerialPort->status.cbInQue > 0 )
	{
		if( pSerialPort->status.cbInQue > maxRead )
		{
			toRead		=	maxRead;
			moreQueued	=	true;
		}
		else
			toRead		=	pSerialPort->status.cbInQue;
	}

	bool success = ReadFile(pSerialPort->handle, buffer, toRead, &bytesRead, NULL);

	if( !success )
	{
		return 0;
	}

	lua_pushlstring(L, buffer, bytesRead);
	lua_pushboolean(L, moreQueued);
	return 2;
}


int Serial_port_lua::write(lua_State *L)
{
	int top = lua_gettop(L);
	if( top != 2 )
		wrongArgs(L);
	checkType(L, LT_USERDATA, 1);
	checkType(L, LT_STRING | LT_NUMBER, 2);

	SerialPort *pSerialPort = *static_cast<SerialPort **>(lua_touserdata(L, 1));

	size_t sendLen;
	DWORD bytesSent;
	DWORD errors;
	const char *toSend = lua_tolstring(L, 2, &sendLen);

	bool success = WriteFile(pSerialPort->handle, toSend, sendLen, &bytesSent, 0);

	if( !success )
	{
		ClearCommError(pSerialPort->handle, &errors, &pSerialPort->status);
		lua_pushboolean(L, false);
	}
	else
		lua_pushboolean(L, true);

	return 1;
}
