#include "lua-api.h"
#include "3rd/jsmn/jsmn.h"

#define MAXTOKEN 1024

// define unescape sequence

static int token_to_object(lua_State *L, jsmntok_t* t, const char* s, int cid);
static int l_json_parser(lua_State *L, const char* s);

//void header(int,const char*);
static int l_json_decode_s (lua_State *L) {
	const char* s = luaL_checkstring(L,1);
	return l_json_parser(L,s);
}

static int l_json_decode_f (lua_State *L) {
	// read the entire file
	char * buffer = 0;
	long length;
	const char* ph = luaL_checkstring(L,1);
	FILE * f = fopen (ph, "rb");

	if (f)
	{
	  fseek (f, 0, SEEK_END);
	  length = ftell (f);
	  fseek (f, 0, SEEK_SET);
	  buffer = malloc (length+1);
	  if (buffer)
	  {
	    fread (buffer, 1, length, f);
	  }
	  fclose (f);
	}

	if (buffer)
	{
		buffer[length] = '\0';
	   l_json_parser(L,buffer);
	   free(buffer);
	   return 1;
	}
	else
	{
		lua_pushnil(L);
		return 1;
	}
	
}
static int  process_token_object(lua_State* L, jsmntok_t* t, const char* s, int cid)
{
	lua_newtable(L);
	int id = cid+1;
	//printf("%d\n", t[cid].size);
	for(int i = 0; i < t[cid].size; i++)
	{
		char*str = strndup(s+t[id].start, t[id].end-t[id].start);
		lua_pushstring(L,str);
		free(str);
		id = token_to_object(L,t,s,id+1);
		lua_settable(L, -3);
	}
	return id;
}
static int process_token_array(lua_State* L, jsmntok_t* t, const char* s, int cid)
{
	lua_newtable(L);
	int id = cid+1;
	for(int i = 1; i <= t[cid].size; i++)
	{
		lua_pushnumber(L,i);
		id = token_to_object(L,t,s,id);
		lua_settable(L, -3);
	}
	return id;
}

static void stackDump (lua_State *L) {
      int i;
      int top = lua_gettop(L);
      for (i = 1; i <= top; i++) {  /* repeat for each level */
        int t = lua_type(L, i);
        switch (t) {
    
          case LUA_TSTRING:  /* strings */
            printf("`%s' \n", lua_tostring(L, i));
            break;
    
          case LUA_TBOOLEAN:  /* booleans */
            printf(lua_toboolean(L, i) ? "true\n" : "false\n");
            break;
    
          case LUA_TNUMBER:  /* numbers */
            printf("%g\n", lua_tonumber(L, i));
            break;
    
          default:  /* other values */
            printf("%s\n", lua_typename(L, t));
            break;
    
        }
        printf("  ");  /* put a separator */
      }
      printf("\n");  /* end the listing */
    }
	
static int process_token_string(lua_State* L, jsmntok_t* t, const char* s, int cid)
{
	// unescape a string
	//const char* search_token[8] = {"\\\\","\\\"","\\n","\\t","\\b","\\f","\\r","\\/"};
	//const char* replace_token[8] = {"\\","\"","\n","\t","\b","\f","\r","/"};
	char * str = strndup(s+t[cid].start, t[cid].end-t[cid].start);
	// un escape the string
	lua_getglobal(L, "utils");
	lua_getfield(L, -1, "unescape");
	lua_pushstring(L,str);
	if (lua_pcall(L, 1, 1, 0) != 0)
	        printf("Error running function `unescape': %s\n",lua_tostring(L, -1));
	if(str) free(str);
	str = luaL_checkstring(L,-1);
	lua_settop(L, -3);
	lua_pushstring(L,str);
	//stackDump(L);
	//lua_pushstring(L, str);
	//printf("%s\n",strndup(s+t[cid].start, t[cid].end-t[cid].start) );
	return cid+1;
}
static int process_token_primitive(lua_State* L, jsmntok_t* t, const char* s, int cid)
{
	//printf("%s\n",strndup(s+t[cid].start, t[cid].end-t[cid].start) );
	char c = s[t[cid].start];
	char *str;
	switch(c)
	{
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
		case '+':
		case '-':
			str = strndup(s+t[cid].start, t[cid].end-t[cid].start);
			lua_pushnumber(L,atof(str));
			free(str);
			break;
		
		case 't': lua_pushboolean(L,1); break;
		case 'f': lua_pushboolean(L,0); break;
		default: lua_pushnil(L);break;
		
	}
	return cid+1;
}
static int token_to_object(lua_State *L, jsmntok_t* t, const char* s, int cid)
{
	switch(t[cid].type)
	{
		case JSMN_OBJECT:
			return process_token_object(L,t,s,cid);
			break;
		case JSMN_ARRAY:
			return process_token_array(L,t,s,cid);
			break;
		case JSMN_STRING:
			return process_token_string(L,t,s,cid);
			break;
		
		case JSMN_PRIMITIVE:
			return process_token_primitive(L,t,s,cid);
			break;
		
		default:
			return cid + t[cid].size; break;
		
	}
}

static int l_json_parser(lua_State *L, const char* s)
{
	jsmn_parser p;
	jsmntok_t t[MAXTOKEN]; 

	jsmn_init(&p);
	int r = jsmn_parse(&p, s, strlen(s), t, sizeof(t)/sizeof(t[0]));
	if (r < 0) {
		LOG("Failed to parse JSON: %d\n", r);
		return 0;
	}
	token_to_object(L,t,s,0);
	return 1;
}
static const struct luaL_Reg _json [] = {
	{"decodeString", l_json_decode_s},
	{"decodeFile", l_json_decode_f},
	{NULL,NULL}
};

int luaopen_json(lua_State *L)
{
	luaL_newlib(L, _json);
	return 1;
}