#include "main.h"
#include <lua.h>
#include <lauxlib.h>
#include "lib_io.h"

static int lib_gc(lua_State *L)
{
	Lua_File *data = luaL_checkudata(L,1,"FILE*");
	if (data->f)
		fclose(data->f);
	data->f = NULL;
	return 0;
}

#define MAXLINELEN 1024
static int iterator_readline(lua_State *L)
{
	Lua_File *data = luaL_checkudata(L,1,"FILE*");
	if (!data->f)
		return 0;
	static char buf[MAXLINELEN];
	if(!fgets(buf, MAXLINELEN, data->f))
		return 0;
	char *p = &buf[strlen(buf)-1];
	while ((*p == '\r' || *p == '\n' || *p == ' ') && p > buf) {
		*p = '\0';
		p--;
	}
	lua_pushstring(L,buf);
	return 1;
}

static int lib_lines(lua_State *L)
{
	/*Lua_File *data = */luaL_checkudata(L,1,"FILE*");
	lua_pushcfunction(L,iterator_readline);
	lua_pushvalue(L,1);
	return 2;
}

static int lib_getnum(lua_State *L)
{
	Lua_File *data = luaL_checkudata(L,1,"FILE*");
	if (!data->f)
		return 0;
	int bytes = luaL_checkint(L,2);
	Uint8 unsign = (lua_gettop(L) >= 3 && lua_toboolean(L,3));
	switch (bytes)
	{
	case 1: {
		Sint8 buf;
		if (fread(&buf,bytes,1,data->f) <= 0)
			return 0; // EOF
		if (unsign)
			lua_pushinteger(L,(unsigned)buf);
		else
			lua_pushinteger(L,buf);
		return 1;
	}
	case 2: {
		Sint16 buf;
		if (fread(&buf,bytes,1,data->f) <= 0)
			return 0; // EOF
		if (data->endian == 1)
			buf = SDL_SwapBE16(buf);
		else if (data->endian == 2)
			buf = SDL_SwapLE16(buf);
		if (unsign)
			lua_pushinteger(L,(unsigned)buf);
		else
			lua_pushinteger(L,buf);
		return 1;
	}
	case 4: {
		Sint32 buf;
		if (fread(&buf,bytes,1,data->f) <= 0)
			return 0; // EOF
		if (data->endian == 1)
			buf = SDL_SwapBE32(buf);
		else if (data->endian == 2)
			buf = SDL_SwapLE32(buf);
		if (unsign)
			lua_pushinteger(L,(unsigned)buf);
		else
			lua_pushinteger(L,buf);
		return 1;
	}
	default:
		return luaL_error(L,"getnum bytes must be 1, 2, or 4!");
	}
}

static int lib_getstr(lua_State *L)
{
	Lua_File *data = luaL_checkudata(L,1,"FILE*");
	if (!data->f)
		return 0;
	int bytes = luaL_checkint(L,2);
	char preserve = (lua_gettop(L) >= 3 && lua_toboolean(L,3));
	char buf[bytes+1];
	if (fread(buf,1,bytes,data->f) <= 0)
		return 0; // EOF
	if (preserve) // Push with 0s embedded
		lua_pushlstring(L,buf,bytes);
	else { // NULL-terminated string
		buf[bytes] = '\0';
		lua_pushstring(L,buf);
	}
	return 1;
}

static int lib_putnum(lua_State *L)
{
	Lua_File *data = luaL_checkudata(L,1,"FILE*");
	if (!data->f)
		return 0;
	lua_Integer num = luaL_checkinteger(L,2);
	int bytes = luaL_checkint(L,3);
	switch (bytes)
	{
	case 1: {
		Sint8 buf = num;
		fwrite(&buf,bytes,1,data->f);
		break;
	}
	case 2: {
		Sint16 buf = num;
		if (data->endian == 1)
			buf = SDL_SwapBE16(buf);
		else if (data->endian == 2)
			buf = SDL_SwapLE16(buf);
		fwrite(&buf,bytes,1,data->f);
		break;
	}
	case 4: {
		Sint32 buf = num;
		if (data->endian == 1)
			buf = SDL_SwapBE32(buf);
		else if (data->endian == 2)
			buf = SDL_SwapLE32(buf);
		fwrite(&buf,bytes,1,data->f);
		break;
	}
	default:
		return luaL_error(L,"putnum bytes must be 1, 2, or 4!");
	}
	return 0;
}

static int lib_putstr(lua_State *L)
{
	Lua_File *data = luaL_checkudata(L,1,"FILE*");
	if (!data->f)
		return 0;
	size_t strlen;
	const char *string = luaL_checklstring(L,2,&strlen);
	size_t len = luaL_optint(L,3,strlen);
	char buf[len];
	strncpy(buf,string,len);
	if (len != strlen) {
		const char fill = luaL_optstring(L,4,"\0")[0];
		int i;
		for (i = strlen; i < len; i++)
			buf[i] = fill;
	}
	fwrite(buf,1,len,data->f);
	return 0;
}

static int lib_seek(lua_State *L)
{
	Lua_File *data = luaL_checkudata(L,1,"FILE*");
	if (!data->f)
		return 0;
	long int offset = luaL_optint(L,2,0);
	int origin = luaL_optint(L,3,SEEK_CUR);
	lua_pushboolean(L,!fseek(data->f,offset,origin));
	return 1;
}

static int lib_tell(lua_State *L)
{
	Lua_File *data = luaL_checkudata(L,1,"FILE*");
	if (!data->f)
		return 0;
	lua_pushinteger(L,ftell(data->f));
	return 1;
}

static luaL_Reg lib[] = {
	{"__gc",lib_gc},
	{"close",lib_gc},
	{"lines",lib_lines},
	{"getnum",lib_getnum},
	{"getstr",lib_getstr},
	{"putnum",lib_putnum},
	{"putstr",lib_putstr},
	{"seek",lib_seek},
	{"tell",lib_tell},
	{NULL,NULL}
};

void agllib_file(lua_State *L)
{
	luaL_newmetatable(L,"FILE*");
	lua_pushvalue(L,-1);
	lua_setfield(L,-2,"__index");
	luaL_register(L,NULL,lib);
	lua_pop(L,1);
}
