/*
 *
 * Created by jiaqiang.ye@lnxall.com
 *
 * Simple C Module for Rust Application
 *
 * 2022/01/09
 *
 */

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <regex.h>

#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
#include <math.h>

#include "cmod.h"
#define ALL_UPVALUE_FUNC \
	"lxf_ALL_UPVALUES"

#define LUA_DDEFAULT_ENV \
	"dyutils      = require \"dyutils\"\n"    \
	"bit          = require \"bit\"\n"        \
	"lpack        = require \"lua_pack\"\n"   \
	"cjson        = require \"cjson\"\n"      \
	"base64       = require \"base64\"\n"     \
	"syslog       = require \"syslog\"\n"     \
	"bcd          = require \"bcd\"\n"        \
	"invoker      = require \"invoker\""

unsigned char * cmod_malloc(unsigned long msize, unsigned long * mptr)
{
    unsigned char * rval;
    rval = (unsigned char *) malloc((size_t) msize);
    if (mptr != NULL)
        *mptr = (unsigned long) rval;
    return rval;
}

void cmod_memset(unsigned long memptr,
    int value, unsigned long msize)
{
    void * mptr = (void *) memptr;
    if (mptr != NULL && msize > 0)
        memset(mptr, value, (size_t) msize);
}

void cmod_free(unsigned long memptr)
{
    void * mptr = (void *) memptr;
    if (mptr != NULL)
        free(mptr);
}

long cmod_strtol(const char * mptr, int * perror, int base)
{
    int error;
    long ret;
    char * ends;

    if (mptr == NULL || *mptr == '\0') {
        error = EINVAL;
        *perror = EINVAL;
        return -1;
    }

    ends = NULL;
    errno = 0;
    ret = strtol(mptr, &ends, base);
    error = errno;
    if (error || ends == mptr) {
        *perror = error > 0 ? error : EINVAL;
        return -1;
    }

    *perror = 0;
    return ret;
}

unsigned long cmod_strtoul(const char * mptr, int * perror, int base)
{
    int error;
    char * ends;
    unsigned long ret;

    if (mptr == NULL || *mptr == '\0') {
        error = EINVAL;
        *perror = EINVAL;
        return ~0ul;
    }

    ends = NULL;
    errno = 0;
    ret = strtoul(mptr, &ends, base);
    error = errno;
    if (error || ends == mptr) {
        *perror = error > 0 ? error : EINVAL;
        return ~0ul;
    }

    *perror = 0;
    return ret;
}

double cmod_strtod(const char * mptr, int * perror)
{
	double ret;
	char * ends;
	int error = 0;

	ret = 0;
	if (mptr == NULL || *mptr == '\0') {
		error = EINVAL;
		*perror = error;
		return ret;
	}

	ends = NULL;
	errno = 0;
	ret = strtod(mptr, &ends);
	error = errno;
	if (error || ends == mptr) {
		*perror = error > 0 ? error : EINVAL;
		return 0;
	}

	if (isnan(ret) != 0) {
		*perror = EINVAL;
		return 0;
	}
	return ret;
}

#define CMOD_GWSN_SIZE 128
char * cmod_gwsn(void)
{
	int ret;
	FILE * pfil;
	regex_t regsn;
	char tbuf[CMOD_GWSN_SIZE], * rval;
	const char * regstr = "^\\s*sn\\s*=\\s*([[:alnum:]]+)\\s*$";

	pfil = NULL;
	rval = NULL;
	memset(&regsn, 0, sizeof(regsn));
	ret = regcomp(&regsn, regstr, REG_EXTENDED | REG_NEWLINE);
	if (ret) {
		fprintf(stderr, "Error, failed to compile regex '%s': %d\n", regstr, ret);
		fflush(stderr);
		return rval;
	}

	pfil = fopen("/app/config/fac.ini", "rb");
	if (pfil == NULL) {
		const char * gwsn = getenv("GWSN_YOUCAN");
		if (gwsn && gwsn[0])
			rval = strdup(gwsn);
		goto err0;
	}

	rval = (char *) malloc(CMOD_GWSN_SIZE);
	if (rval == NULL) {
		fprintf(stderr, "Error, system out of memory: %d", CMOD_GWSN_SIZE);
		fflush(stderr);
		goto err0;
	}
	rval[0] = '\0';

	while (fgets(tbuf, sizeof(tbuf) - 1, pfil) != NULL) {
		regmatch_t regm[2];

		tbuf[sizeof(tbuf) - 1] = '\0';
		regm[0].rm_so = regm[0].rm_eo = 0;
		regm[1].rm_so = regm[1].rm_eo = 0;

		ret = regexec(&regsn, tbuf, 0x2, regm, 0);
		if (ret == 0) {
			regoff_t idx, jdx;

			jdx = 0;
			idx = regm[1].rm_so;
			while (idx < regm[1].rm_eo) {
				rval[jdx++] = tbuf[idx++];
				if (jdx >= CMOD_GWSN_SIZE - 1)
					break;
			}
			rval[jdx++] = '\0';
			break;
		}
	}

	if (rval[0] == '\0') {
		free(rval);
		rval = NULL;
	}

err0:
	if (pfil != NULL)
		fclose(pfil);
	regfree(&regsn);
	return rval;
}
#undef CMOD_GWSN_SIZE

static void clua_value_drop(struct clua_value * pval)
{
	if (pval->val_type == CMODLUA_VAL_STR && pval->val_cmod.val_str != NULL)
		free(pval->val_cmod.val_str);
	pval->val_type = CMODLUA_VAL_NONE;
	pval->val_cmod.val_float = 0;
}

unsigned long clua_load(const char * script)
{
	lua_State * L;
	const char * lua_env = LUA_DDEFAULT_ENV;

	L = luaL_newstate();
	if (L == NULL) {
		fputs("Error, failed to create new Lua machine!\n", stderr);
		fflush(stderr);
		return 0ul;
	}

	luaL_openlibs(L);
	if (luaL_dostring(L, lua_env)) {
		fputs("Error, failed to load LUA_DDEFAULT_ENV!\n", stderr);
		lua_close(L);
		return 0ul;
	}

	if (luaL_dostring(L, script)) {
		fprintf(stderr, "Error, failed to load Lua script:\n%s\n", script);
		fflush(stderr);
		lua_close(L);
		return 0ul;
	}

	return (unsigned long) L;
}

void clua_free(unsigned long luast)
{
	lua_State * L;
	L = (lua_State *) luast;
	if (L != NULL)
		lua_close(L);
}

char * clua_getupval(unsigned long luast, int idx)
{
	char * rval;
	int ntop, nt;
	lua_State * L;
	const char * uvstr;
	const char * func = ALL_UPVALUE_FUNC;

	L = (lua_State *) luast;
	if (L == NULL) {
		fputs("Error, invalid null Lua machine!\n", stderr);
		fflush(stderr);
		return NULL;
	}

	nt = lua_gettop(L);
	lua_getglobal(L, func);
	ntop = lua_gettop(L);
	if (ntop != (nt + 1) || lua_type(L, ntop) != LUA_TFUNCTION) {
		fprintf(stderr, "Error, failed to fetch function: '%s'\n", func);
		fflush(stderr);
		if (ntop != nt)
			lua_settop(L, nt);
		return NULL;
	}

	uvstr = lua_getupvalue(L, ntop, idx);
	if (uvstr == NULL || uvstr[0] == '\0') {
		lua_settop(L, nt);
		return NULL;
	}

	rval = strdup(uvstr);
	lua_settop(L, nt);
	if (rval == NULL) {
		fputs("Error, system out of memory!\n", stderr);
		fflush(stderr);
	}
	return rval;
}

static int clua_fetch_val(lua_State * L, const char * vname, struct clua_value * pval)
{
	int dtype;

	dtype = lua_type(L, -1);
	if (dtype == LUA_TNUMBER) {
		lua_Number l_num;
		lua_Integer l_int;
		l_num = lua_tonumber(L, -1);
		if (isnan(l_num) != 0) {
			fprintf(stderr, "Error, not a number for '%s'\n", vname);
			fflush(stderr);
			return -1;
		}

		l_int = lua_tointeger(L, -1);
		if (l_num == (lua_Number) l_int) {
			if (l_int <= 0x7FFFFFFF) {
				pval->val_type = CMODLUA_VAL_S32;
				pval->val_cmod.val_s32 = (signed int) l_int;
			} else {
				pval->val_type = CMODLUA_VAL_U32;
				pval->val_cmod.val_u32 = (unsigned int) l_int;
			}
		} else {
			pval->val_type = CMODLUA_VAL_FLOAT;
			pval->val_cmod.val_float = (double) l_num;
		}
	} else if (dtype == LUA_TSTRING) {
		char * ustr;
		size_t vlen = 0;
		const char * vstr;
		vstr = lua_tolstring(L, -1, &vlen);
		if (vstr == NULL) {
			fprintf(stderr, "Error, failed to fetch string for '%s'\n", vname);
			fflush(stderr);
			return -1;
		}

		ustr = (char *) malloc(vlen + 1);
		if (ustr == NULL) {
			fprintf(stderr, "Error, system out of memory for '%s': %zu\n",
				vname, vlen);
			fflush(stderr);
			return -1;
		}
		if (vlen > 0) {
			memcpy(ustr, vstr, vlen);
		}
		ustr[vlen] = '\0';
		pval->val_type = CMODLUA_VAL_STR;
		pval->val_cmod.val_str = ustr;
		// fprintf(stderr, "Allocated string: %p\n", ustr);
		// fflush(stderr);
	} else {
		fprintf(stderr, "Error, unsupport type for '%s': %d\n", vname, dtype);
		fflush(stderr);
		return -1;
	}
	return 0;
}

int clua_getval(unsigned long luast, int idx, struct clua_value * pval)
{
	int ntop, nt;
	lua_State * L;
	const char * uvstr;
	const char * func = ALL_UPVALUE_FUNC;

	pval->val_type = CMODLUA_VAL_NONE;
	pval->val_cmod.val_float = 0;
	L = (lua_State *) luast;
	if (L == NULL) {
		fputs("Error, invalid null Lua machine!\n", stderr);
		fflush(stderr);
		return -1;
	}

	nt = lua_gettop(L);
	lua_getglobal(L, func);
	ntop = lua_gettop(L);
	if (ntop != (nt + 1) || lua_type(L, ntop) != LUA_TFUNCTION) {
		fprintf(stderr, "Error, failed to fetch function: '%s'\n", func);
		fflush(stderr);
		if (ntop != nt)
			lua_settop(L, nt);
		return -1;
	}

	uvstr = lua_getupvalue(L, ntop, idx);
	if (uvstr == NULL || uvstr[0] == '\0') {
		fprintf(stderr, "Error, failed to fetch upvalue at %d\n", idx);
		fflush(stderr);
		lua_settop(L, nt);
		return -1;
	}

	ntop = clua_fetch_val(L, uvstr, pval);
	lua_settop(L, nt);
	return ntop;
}

static int luaval_push(lua_State * L, const struct clua_value * pval)
{
	switch (pval->val_type) {
	case CMODLUA_VAL_S32:
		lua_pushinteger(L, (lua_Integer) pval->val_cmod.val_s32);
		break;

	case CMODLUA_VAL_U32:
		if (pval->val_cmod.val_u32 > 0x7FFFFFFFu) {
			lua_pushnumber(L, (lua_Number) pval->val_cmod.val_u32);
		} else {
			lua_pushinteger(L, (lua_Integer) pval->val_cmod.val_u32);
		}
		break;

	case CMODLUA_VAL_FLOAT: {
		int dopu = 1;
		double vflt = pval->val_cmod.val_float;
		if (vflt >= -2147483648.0 && vflt <= 2147483647.0) { // 0x7FFF_FFFF
			int vall = (int) vflt;
			if (vflt == (double) vall) {
				dopu = 0;
				lua_pushinteger(L, (lua_Integer) vall);
			}
		}
		if (dopu)
			lua_pushnumber(L, (lua_Number) vflt);
		break;
	}

	case CMODLUA_VAL_STR:
		if (pval->val_cmod.val_str != NULL)
			lua_pushstring(L, pval->val_cmod.val_str);
		else {
			fputs("Error, invalid string for table-push\n", stderr);
			fflush(stderr);
			return -1;
		}
		break;

	default:
		fprintf(stderr, "Error, invalid data type for table-push: %u\n", pval->val_type);
		fflush(stderr);
		return -1;
	}
	return 0;
}

int clua_setval(unsigned long luast, int idx, const struct clua_value * pval)
{
	int ntop, nt;
	lua_State * L;
	const char * uvstr;
	const char * func = ALL_UPVALUE_FUNC;

	L = (lua_State *) luast;
	if (L == NULL) {
		fputs("Error, invalid null Lua machine!\n", stderr);
		fflush(stderr);
		return -1;
	}

	nt = lua_gettop(L);
	lua_getglobal(L, func);
	ntop = lua_gettop(L);
	if (ntop != (nt + 1) || lua_type(L, ntop) != LUA_TFUNCTION) {
		fprintf(stderr, "Error, failed to fetch function: '%s'\n", func);
		fflush(stderr);
		if (ntop != nt)
			lua_settop(L, nt);
		return -1;
	}

	if (luaval_push(L, pval) < 0) {
		lua_settop(L, nt);
		return -1;
	}

	uvstr = lua_setupvalue(L, ntop, idx);
	if (uvstr == NULL || uvstr[0] == '\0') {
		fprintf(stderr, "Error, failed to update upvalue at %d\n", idx);
		lua_settop(L, nt);
		return -1;
	}

	lua_settop(L, nt);
	return 0;
}

int clua_call(unsigned long luast, const char * funcn,
	struct clua_value * pval, const struct clua_value * parg)
{
	lua_State * L;
	int ntop, nt, ret;

	clua_value_drop(pval);
	L = (lua_State *) luast;
	if (L == NULL) {
		fputs("Error, invalid null Lua machine!\n", stderr);
		fflush(stderr);
		return -1;
	}

	nt = lua_gettop(L);
	lua_getglobal(L, funcn);
	ntop = lua_gettop(L);
	if (ntop != (nt + 1) || lua_type(L, ntop) != LUA_TFUNCTION) {
		fprintf(stderr, "Error, failed to fetch function: '%s'\n", funcn);
		fflush(stderr);
		if (ntop != nt)
			lua_settop(L, nt);
		return -1;
	}

	if (parg != NULL && luaval_push(L, parg) < 0) {
		lua_settop(L, nt);
		return -1;
	}

	ret = lua_pcall(L, parg ? 1 : 0, 1, 0);
	if (ret != 0) {
		fprintf(stderr, "Error, failed call lua function '%s': %d\n", funcn, ret);
		lua_settop(L, nt);
		return -1;
	}

	ret = clua_fetch_val(L, funcn, pval);
	lua_settop(L, nt);
	return ret;
}

int clua_tpush(unsigned long luast, int maxnum,
	const char * keyn, const struct clua_value * pval)
{
	int ntop, ret;
	lua_State * L;

	L = (lua_State *) luast;
	if (L == NULL) {
		fprintf(stderr, "Error, invalid Lua pointer for '%s'!\n", keyn ? : "nil");
		fflush(stderr);
		return -1;
	}

	if (keyn == NULL || keyn[0] == '\0') {
		fputs("Error, invalid empty key for table-push!\n", stderr);
		fflush(stderr);
		return -1;
	}

	ntop = lua_gettop(L);
	if (ntop <= 0 || lua_type(L, ntop) != LUA_TTABLE) {
		if (ntop < 0) { // is this necessary?
			ntop = 0;
			lua_settop(L, ntop);
		}

		lua_createtable(L, 0, maxnum);
		if ((ntop + 1) != lua_gettop(L)) {
			fprintf(stderr, "Error, failed to create table for '%s'\n", keyn ? : "nil");
			fflush(stderr);
			lua_settop(L, ntop);
			return -1;
		}
		ntop++;
	}

	lua_pushstring(L, keyn);
	if (luaval_push(L, pval) < 0) {
		lua_settop(L, ntop);
		return 0;
	}

	lua_settable(L, ntop);
	ret = lua_gettop(L);
	if (ret != ntop) {
		fprintf(stderr, "Error, failed to push table element: %s\n", keyn);
		fflush(stderr);
		lua_settop(L, ntop);
	}
	return ntop;
}

int clua_tcall(unsigned long luast, const char * funcn, struct clua_value * pval)
{
	lua_State * L;
	int ntop, nt, ret;

	clua_value_drop(pval);
	L = (lua_State *) luast;
	if (L == NULL) {
		fputs("Error, invalid null Lua machine!\n", stderr);
		fflush(stderr);
		return -1;
	}

	nt = lua_gettop(L);
	if (nt <= 0 || lua_type(L, nt) != LUA_TTABLE) {
		fprintf(stderr, "Error, table not found for table-call: %d\n", nt);
		fflush(stderr);
		lua_settop(L, 0);
		return -1;
	}

	lua_getglobal(L, funcn);
	ntop = lua_gettop(L);
	if (ntop != (nt + 1) || lua_type(L, ntop) != LUA_TFUNCTION) {
		fprintf(stderr, "Error, failed to fetch function: '%s'\n", funcn);
		fflush(stderr);
		lua_settop(L, 0);
		return -1;
	}

	lua_insert(L, ntop - 1);
	ret = lua_pcall(L, 1, 1, 0);
	if (ret != 0) {
		fprintf(stderr, "Error, failed call lua function '%s': %d\n", funcn, ret);
		lua_settop(L, 0);
		return -1;
	}

	ret = clua_fetch_val(L, funcn, pval);
	lua_settop(L, 0);
	return ret;
}

int clua_topval(unsigned long luast, int ntop)
{
	int ret;
	lua_State * L;

	L = (lua_State *) luast;
	if (L == NULL) {
		fputs("Error, invalid null Lua state matchine!\n", stderr);
		fflush(stderr);
		return 0;
	}

	ret = lua_gettop(L);
	if (ntop >= 0)
		lua_settop(L, ntop);

	return ret;
}
