/*
    Copyright (c) 2006-2020 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "mod-math.h"
#include "hawk-prv.h"

#if !defined(_GNU_SOURCE)
#	define _GNU_SOURCE
#endif
#include <stdlib.h>
#include <sys/time.h>
#include <math.h>
#if defined(HAVE_QUADMATH_H)
#	include <quadmath.h>
#elif defined(HAWK_USE_FLTMAX) && (HAWK_SIZEOF_FLT_T == 16) && defined(HAWK_FLTMAX_REQUIRE_QUADMATH)
	/* the header file doesn't exist while the library is available */
	extern __float128 ceilq (__float128);
	extern __float128 floorq (__float128);
	extern __float128 roundq (__float128);
	extern __float128 sinq (__float128);
	extern __float128 sinhq (__float128);
	extern __float128 asinq (__float128);
	extern __float128 cosq (__float128);
	extern __float128 coshq (__float128);
	extern __float128 acosq (__float128);
	extern __float128 tanq (__float128);
	extern __float128 tanhq (__float128);
	extern __float128 atanq (__float128);
	extern __float128 atan2q (__float128, __float128);
	extern __float128 logq (__float128);
	extern __float128 log10q (__float128);
	extern __float128 expq (__float128);
	extern __float128 sqrtq (__float128);
#endif

#if !defined(HAWK_HAVE_CFG_H)
#	if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
#		define HAVE_CEIL
#		define HAVE_FLOOR
#		if !defined(__WATCOMC__) && !defined(__BORLANDC__)
#		define HAVE_ROUND
#		endif
#		define HAVE_SINH
#		define HAVE_COSH
#		define HAVE_TANH
#		define HAVE_ASIN
#		define HAVE_ACOS

#		define HAVE_SIN
#		define HAVE_COS
#		define HAVE_TAN
#		define HAVE_ATAN
#		define HAVE_ATAN2
#		define HAVE_LOG
#		define HAVE_LOG10
#		define HAVE_EXP
#		define HAVE_SQRT
#	endif
#endif
typedef struct modctx_t
{
	unsigned int seed;
#if defined(HAVE_INITSTATE_R) && defined(HAVE_SRANDOM_R) && defined(HAVE_RANDOM_R)
	struct random_data prand;
	char prand_bin[256]; /* or hawk_uint8_t? */
#endif
} modctx_t;

static int fnc_math_1 (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi, hawk_math1_t f)
{
	hawk_oow_t nargs;
	hawk_val_t* a0;
	hawk_flt_t rv;
	hawk_val_t* r;
	int n;

	nargs = hawk_rtx_getnargs (rtx);
	HAWK_ASSERT (nargs == 1);

	a0 = hawk_rtx_getarg(rtx, 0);

	n = hawk_rtx_valtoflt(rtx, a0, &rv);
	if (n <= -1) return -1;

	r = hawk_rtx_makefltval (rtx, f(hawk_rtx_gethawk(rtx), rv));
	if (r == HAWK_NULL) return -1;

	hawk_rtx_setretval (rtx, r);
	return 0;
}

static int fnc_math_2 (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi, hawk_math2_t f)
{
	hawk_oow_t nargs;
	hawk_val_t* a0, * a1;
	hawk_flt_t rv0, rv1;
	hawk_val_t* r;
	int n;

	nargs = hawk_rtx_getnargs (rtx);
	HAWK_ASSERT (nargs == 2);

	a0 = hawk_rtx_getarg(rtx, 0);
	a1 = hawk_rtx_getarg(rtx, 1);

	n = hawk_rtx_valtoflt(rtx, a0, &rv0);
	if (n <= -1) return -1;

	n = hawk_rtx_valtoflt (rtx, a1, &rv1);
	if (n <= -1) return -1;

	r = hawk_rtx_makefltval (rtx, f(hawk_rtx_gethawk(rtx), rv0, rv1));
	if (r == HAWK_NULL) return -1;

	hawk_rtx_setretval (rtx, r);
	return 0;
}


static hawk_flt_t math_ceil (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_CEILQ)
	return ceilq (x);
#elif defined(HAVE_CEILL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return ceill (x);
#elif defined(HAVE_CEIL)
	return ceil (x);
#elif defined(HAVE_CEILF)
	return ceilf (x);
#else
	#error ### no ceil function available ###
#endif
}

static hawk_flt_t math_floor (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_FLOORQ)
	return floorq (x);
#elif defined(HAVE_FLOORL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return floorl (x);
#elif defined(HAVE_FLOOR)
	return floor (x);
#elif defined(HAVE_FLOORF)
	return floorf (x);
#else
	#error ### no floor function available ###
#endif
}

static hawk_flt_t math_round (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_ROUNDQ)
	return roundq (x);
#elif defined(HAVE_ROUNDL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return roundl (x);
#elif defined(HAVE_ROUND)
	return round (x);
#elif defined(HAVE_ROUNDF)
	return roundf (x);
#else

	hawk_flt_t f, d;

	f = math_floor(hawk, x);
	d = x - f; /* get fraction */

	if (d > (hawk_flt_t)0.5)
	{
		/* round up to the nearest */
		f =  f + (hawk_flt_t)1.0;
	}
	else if (d == (hawk_flt_t)0.5)
	{
	#if 1
		/* round half away from zero */
		if (x >= 0)
		{
			f = x + (hawk_flt_t)0.5;
		}
		else
		{
			f = x - (hawk_flt_t)0.5;
		}
	#else
		/* round half to even - C99's rint() does this, i guess. */
		d = f - (hawk_flt_t)2.0 * math_floor(hawk, f * (hawk_flt_t)0.5);
		if (d == (hawk_flt_t)1.0) f =  f + (hawk_flt_t)1.0;
	#endif
	}

	/* this implementation doesn't keep the signbit for -0.0.
	 * The signbit() function defined in C99 may get used to
	 * preserve the sign bit. but this is a fall-back rountine
	 * for a system without round also defined in C99.
	 * don't get annoyed by the lost sign bit for the value of 0.0.
	 */

	return f;

#endif
}

static hawk_flt_t math_sinh (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_SINHQ)
	return sinhq (x);
#elif defined(HAVE_SINHL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return sinhl (x);
#elif defined(HAVE_SINH)
	return sinh (x);
#elif defined(HAVE_SINHF)
	return sinhf (x);
#else
	#error ### no sinh function available ###
#endif
}

static hawk_flt_t math_cosh (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_COSHQ)
	return coshq (x);
#elif defined(HAVE_COSHL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return coshl (x);
#elif defined(HAVE_COSH)
	return cosh (x);
#elif defined(HAVE_COSHF)
	return coshf (x);
#else
	#error ### no cosh function available ###
#endif
}

static hawk_flt_t math_tanh (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_TANHQ)
	return tanhq (x);
#elif defined(HAVE_TANHL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return tanhl (x);
#elif defined(HAVE_TANH)
	return tanh (x);
#elif defined(HAVE_TANHF)
	return tanhf (x);
#else
	#error ### no tanh function available ###
#endif
}

static hawk_flt_t math_asin (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_ASINQ)
	return asinq (x);
#elif defined(HAVE_ASINL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return asinl (x);
#elif defined(HAVE_ASIN)
	return asin (x);
#elif defined(HAVE_ASINF)
	return asinf (x);
#else
	#error ### no asin function available ###
#endif
}

static hawk_flt_t math_acos (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_ACOSQ)
	return acosq (x);
#elif defined(HAVE_ACOSL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return acosl (x);
#elif defined(HAVE_ACOS)
	return acos (x);
#elif defined(HAVE_ACOSF)
	return acosf (x);
#else
	#error ### no acos function available ###
#endif
}

/* ----------------------------------------------------------------------- */


static hawk_flt_t math_sin (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_SINQ)
	return sinq (x);
#elif defined(HAVE_SINL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return sinl (x);
#elif defined(HAVE_SIN)
	return sin (x);
#elif defined(HAVE_SINF)
	return sinf (x);
#else
	#error ### no sin function available ###
#endif
}

static hawk_flt_t math_cos (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_COSQ)
	return cosq (x);
#elif defined(HAVE_COSL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return cosl (x);
#elif defined(HAVE_COS)
	return cos (x);
#elif defined(HAVE_COSF)
	return cosf (x);
#else
	#error ### no cos function available ###
#endif
}

static hawk_flt_t math_tan (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_TANQ)
	return tanq (x);
#elif defined(HAVE_TANL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return tanl (x);
#elif defined(HAVE_TAN)
	return tan (x);
#elif defined(HAVE_TANF)
	return tanf (x);
#else
	#error ### no tan function available ###
#endif
}

static hawk_flt_t math_atan (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_ATANQ)
	return atanq (x);
#elif defined(HAVE_ATANL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return atanl (x);
#elif defined(HAVE_ATAN)
	return atan (x);
#elif defined(HAVE_ATANF)
	return atanf (x);
#else
	#error ### no atan function available ###
#endif
}

static hawk_flt_t math_atan2 (hawk_t* hawk, hawk_flt_t x, hawk_flt_t y)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_ATAN2Q)
	return atan2q (x, y);
#elif defined(HAVE_ATAN2L) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return atan2l (x, y);
#elif defined(HAVE_ATAN2)
	return atan2 (x, y);
#elif defined(HAVE_ATAN2F)
	return atan2f (x, y);
#else
	#error ### no atan2 function available ###
#endif
}

static HAWK_INLINE hawk_flt_t math_log (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_LOGQ)
	return logq (x);
#elif defined(HAVE_LOGL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return logl (x);
#elif defined(HAVE_LOG)
	return log (x);
#elif defined(HAVE_LOGF)
	return logf (x);
#else
	#error ### no log function available ###
#endif
}

static hawk_flt_t math_log2 (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_LOG2Q)
	return log2q (x);
#elif defined(HAVE_LOG2L) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return log2l (x);
#elif defined(HAVE_LOG2)
	return log2 (x);
#elif defined(HAVE_LOG2F)
	return log2f (x);
#else
	return math_log(hawk, x) / math_log(hawk, 2.0);
#endif
}

static hawk_flt_t math_log10 (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_LOG10Q)
	return log10q (x);
#elif defined(HAVE_LOG10L) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return log10l (x);
#elif defined(HAVE_LOG10)
	return log10 (x);
#elif defined(HAVE_LOG10F)
	return log10f (x);
#else
	#error ### no log10 function available ###
#endif
}

static hawk_flt_t math_exp (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_EXPQ)
	return expq (x);
#elif defined(HAVE_EXPL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return expl (x);
#elif defined(HAVE_EXP)
	return exp (x);
#elif defined(HAVE_EXPF)
	return expf (x);
#else
	#error ### no exp function available ###
#endif
}

static hawk_flt_t math_sqrt (hawk_t* hawk, hawk_flt_t x)
{
#if defined(HAWK_USE_FLTMAX) && defined(HAVE_SQRTQ)
	return sqrtq (x);
#elif defined(HAVE_SQRTL) && (HAWK_SIZEOF_LONG_DOUBLE > HAWK_SIZEOF_DOUBLE)
	return sqrtl (x);
#elif defined(HAVE_SQRT)
	return sqrt (x);
#elif defined(HAVE_SQRTF)
	return sqrtf (x);
#else
	#error ### no sqrt function available ###
#endif
}

/* ----------------------------------------------------------------------- */

static int fnc_ceil (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_ceil);
}

static int fnc_floor (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_floor);
}

static int fnc_round (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_round);
}

static int fnc_sinh (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_sinh);
}

static int fnc_cosh (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_cosh);
}

static int fnc_tanh (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_tanh);
}

static int fnc_asin (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_asin);
}

static int fnc_acos (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_acos);
}


/* ----------------------------------------------------------------------- */

static int fnc_sin (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_sin);
}

static int fnc_cos (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_cos);
}

static int fnc_tan (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_tan);
}

static int fnc_atan (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_atan);
}

static int fnc_atan2 (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_2 (rtx, fi, math_atan2);
}

static int fnc_log (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_log);
}

static int fnc_log2 (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_log2);
}

static int fnc_log10 (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_log10);
}

static int fnc_exp (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_exp);
}

static int fnc_sqrt (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	return fnc_math_1 (rtx, fi, math_sqrt);
}

/* ----------------------------------------------------------------------- */

static int fnc_rand (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
/*#define RANDV_MAX HAWK_TYPE_MAX(hawk_int_t)*/
#define RANDV_MAX RAND_MAX
	hawk_val_t* r;
	hawk_int32_t randv;
	modctx_t* modctx;

	modctx = (modctx_t*)fi->mod->ctx;
#if defined(HAVE_INITSTATE_R) && defined(HAVE_SRANDOM_R) && defined(HAVE_RANDOM_R)
	random_r (&modctx->prand, &randv);
#elif defined(HAVE_RANDOM)
	randv = random();
#else
	randv = rand();
#endif

	r = hawk_rtx_makefltval(rtx, (hawk_flt_t)randv / RANDV_MAX);
	if (r == HAWK_NULL) return -1;

	hawk_rtx_setretval (rtx, r);
	return 0;
#undef RANDV_MAX
}

static int fnc_srand (hawk_rtx_t* rtx, const hawk_fnc_info_t* fi)
{
	hawk_oow_t nargs;
	hawk_val_t* a0;
	hawk_int_t lv;
	hawk_val_t* r;
	int n;
	hawk_int_t prev;
	modctx_t* modctx;

	modctx = (modctx_t*)fi->mod->ctx;
	nargs = hawk_rtx_getnargs(rtx);
	HAWK_ASSERT (nargs == 0 || nargs == 1);

	prev = modctx->seed;

	if (nargs <= 0)
	{
		hawk_ntime_t tv;
		hawk_get_ntime (&tv);
		modctx->seed = tv.sec + tv.nsec;
	#if defined(HAVE_INITSTATE_R) && defined(HAVE_SRANDOM_R) && defined(HAVE_RANDOM_R)
		srandom_r (modctx->seed, &modctx->prand);
	#elif defined(HAVE_RANDOM)
		srandom (modctx->seed);
	#else
		srand (modctx->seed);
	#endif
	}
	else
	{
		a0 = hawk_rtx_getarg(rtx, 0);
		n = hawk_rtx_valtoint(rtx, a0, &lv);
		if (n <= -1) return -1;
	#if defined(HAVE_INITSTATE_R) && defined(HAVE_SRANDOM_R) && defined(HAVE_RANDOM_R)
		srandom_r (lv, &modctx->prand);
	#elif defined(HAVE_RANDOM)
		srandom (lv);
	#else
		srand (lv);
	#endif
	}

	r = hawk_rtx_makeintval (rtx, prev);
	if (r == HAWK_NULL) return -1;

	hawk_rtx_setretval (rtx, r);
	return 0;
}

/* ----------------------------------------------------------------------- */

static hawk_mod_fnc_tab_t fnctab[] =
{
	/* keep this table sorted for binary search in query(). */
	{ HAWK_T("acos"),    { { 1, 1, HAWK_NULL },     fnc_acos,       0 } },
	{ HAWK_T("asin"),    { { 1, 1, HAWK_NULL },     fnc_asin,       0 } },
	{ HAWK_T("atan"),    { { 1, 1, HAWK_NULL },     fnc_atan,       0 } },
	{ HAWK_T("atan2"),   { { 2, 2, HAWK_NULL },     fnc_atan2,      0 } },
	{ HAWK_T("ceil"),    { { 1, 1, HAWK_NULL },     fnc_ceil,       0 } },
	{ HAWK_T("cos"),     { { 1, 1, HAWK_NULL },     fnc_cos,        0 } },
	{ HAWK_T("cosh"),    { { 1, 1, HAWK_NULL },     fnc_cosh,       0 } },
	{ HAWK_T("exp"),     { { 1, 1, HAWK_NULL },     fnc_exp,        0 } },
	{ HAWK_T("floor"),   { { 1, 1, HAWK_NULL },     fnc_floor,      0 } },
	{ HAWK_T("log"),     { { 1, 1, HAWK_NULL },     fnc_log,        0 } },
	{ HAWK_T("log10"),   { { 1, 1, HAWK_NULL },     fnc_log10,      0 } },
	{ HAWK_T("log2"),    { { 1, 1, HAWK_NULL },     fnc_log2,       0 } },
	{ HAWK_T("rand"),    { { 0, 0, HAWK_NULL },     fnc_rand,       0 } },
	{ HAWK_T("round"),   { { 1, 1, HAWK_NULL },     fnc_round,      0 } },
	{ HAWK_T("sin"),     { { 1, 1, HAWK_NULL },     fnc_sin,        0 } },
	{ HAWK_T("sinh"),    { { 1, 1, HAWK_NULL },     fnc_sinh,       0 } },
	{ HAWK_T("sqrt"),    { { 1, 1, HAWK_NULL },     fnc_sqrt,       0 } },
	{ HAWK_T("srand"),   { { 0, 1, HAWK_NULL },     fnc_srand,      0 } },
	{ HAWK_T("tan"),     { { 1, 1, HAWK_NULL },     fnc_tan,        0 } },
	{ HAWK_T("tanh"),    { { 1, 1, HAWK_NULL },     fnc_tanh,       0 } }
};

static int query (hawk_mod_t* mod, hawk_t* hawk, const hawk_ooch_t* name, hawk_mod_sym_t* sym)
{
	return hawk_findmodsymfnc(hawk, fnctab, HAWK_COUNTOF(fnctab), name, sym);
}

/* TODO: proper resource management */

static int init (hawk_mod_t* mod, hawk_rtx_t* rtx)
{
	return 0;
}

static void fini (hawk_mod_t* mod, hawk_rtx_t* rtx)
{
	/* TODO: anything */
}

static void unload (hawk_mod_t* mod, hawk_t* hawk)
{
	modctx_t* modctx;

	modctx = (modctx_t*)mod->ctx;
	hawk_freemem(hawk, modctx);
}

int hawk_mod_math (hawk_mod_t* mod, hawk_t* hawk)
{
	modctx_t* modctx;
	hawk_ntime_t tv;

	modctx = hawk_allocmem(hawk, HAWK_SIZEOF(*modctx));
	if (modctx == HAWK_NULL) return -1;

	HAWK_MEMSET (modctx, 0, HAWK_SIZEOF(*modctx));

	hawk_get_ntime (&tv);
	modctx->seed = tv.sec + tv.nsec;
#if defined(HAVE_INITSTATE_R) && defined(HAVE_SRANDOM_R) && defined(HAVE_RANDOM_R)
	initstate_r (0, modctx->prand_bin, HAWK_SIZEOF(modctx->prand_bin), &modctx->prand);
	srandom_r (modctx->seed, &modctx->prand);
#elif defined(HAVE_RANDOM)
	srandom (modctx->seed);
#else
	srand (modctx->seed);
#endif

	mod->query = query;
	mod->unload = unload;

	mod->init = init;
	mod->fini = fini;
	mod->ctx = modctx;

	return 0;
}

