/*
 * Copyright (C) 2003-2007 the xine project
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 * gate to the spider monkey javascript interpreter
 *
 * provides functions to generate interpreter instances
 * customized for controlling the xine engine and the gxine
 * frontend.
 *
 */

#ifndef GXINE_SCRIPT_ENGINE_H
#define GXINE_SCRIPT_ENGINE_H

#include <glib.h>

#ifndef XP_UNIX
# define XP_UNIX
#endif
#include <jsapi.h>
#include <pthread.h>

// nice stable mozjs API
#ifndef HAVE_JSFLOAT64
typedef jsdouble JSFloat64;
#endif

#ifndef JSFUN_FAST_NATIVE
// All natives are JSFastNative
#define JSFUN_FAST_NATIVE 0
#endif

#ifdef HAVE_JS_GET_STRING_BYTES
# define SE_JS_ENCODE_STRING(cx, str) JS_GetStringBytes(str)
# define SE_JS_FREE_ENCODED_STRING(cx, str)
#else
# define SE_JS_ENCODE_STRING(cx, str) JS_EncodeString(cx, str)
# define SE_JS_FREE_ENCODED_STRING(cx, str) JS_free(cx, str)
#endif

#ifdef HAVE_JS_NEW_DOUBLE
# define SE_JS_NEW_NUMBER_VAL(cx, d, rval) \
    jsdouble *new_d = JS_NewDouble (cx, d); \
    rval = DOUBLE_TO_JSVAL (new_d)
#else
# define SE_JS_NEW_NUMBER_VAL(cx, d, rval) \
    JS_NewNumberValue (cx, d, &rval)
#endif

#ifdef JS_PROPERTY_OP_HAS_ID_AS_JSID
# define SE_JS_PROPERTY_GETTER_DECLARE(name) \
    static JSBool \
    name(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
# define SE_JS_PROPERTY_GETTER_INIT_VARIABLES(idval) \
    jsval idval; \
    JS_IdToValue (cx, id, &idval)
# define SE_JS_CALL_PROPERTY_GETTER_WITH_JSVAL(getter, cx, obj, propval, vp) __extension__ ({ \
    jsid propid; \
    JS_ValueToId (cx, propval, &propid); \
    JSBool ret = getter (cx, obj, propid, vp); \
    ret; \
})
#else
# define SE_JS_PROPERTY_GETTER_DECLARE(name) \
    static JSBool \
    name(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
# define SE_JS_PROPERTY_GETTER_INIT_VARIABLES(idval) \
    jsval idval = id
# define SE_JS_CALL_PROPERTY_GETTER_WITH_JSVAL(getter, cx, obj, propval, vp) __extension__ ({ \
    JSBool ret = getter (cx, obj, propval, vp); \
    ret; \
})
#endif

#ifdef HAVE_JS_STRICT_PROPERTY_OP
# define SE_JS_PROPERTY_SETTER_DECLARE(name) \
    static JSBool \
    name(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
# define SE_JS_PROPERTY_SETTER_INIT_VARIABLES(idval) \
    jsval idval; \
    JS_IdToValue (cx, id, &idval)
# define SE_JS_CALL_PROPERTY_SETTER_WITH_JSVAL(setter, cx, obj, propval, vp) __extension__ ({ \
    jsid propid; \
    JS_ValueToId (cx, propval, &propid); \
    JSBool ret = setter (cx, obj, propid, JS_FALSE, vp); \
    ret; \
})
#elif JS_PROPERTY_OP_HAS_ID_AS_JSID
# define SE_JS_PROPERTY_SETTER_DECLARE(name) \
    static JSBool \
    name(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
# define SE_JS_PROPERTY_SETTER_INIT_VARIABLES(idval) \
    jsval idval; \
    JS_IdToValue (cx, id, &idval)
# define SE_JS_CALL_PROPERTY_SETTER_WITH_JSVAL(setter, cx, obj, propval, vp) __extension__ ({ \
    jsid propid; \
    JS_ValueToId (cx, propval, &propid); \
    JSBool ret = setter (cx, obj, propid, vp); \
    ret; \
})
#else
# define SE_JS_PROPERTY_SETTER_DECLARE(name) \
    static JSBool \
    name(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
# define SE_JS_PROPERTY_SETTER_INIT_VARIABLES(idval) \
    jsval idval = id
# define SE_JS_CALL_PROPERTY_SETTER_WITH_JSVAL(setter, cx, obj, propval, vp) __extension__ ({ \
    JSBool ret = setter (cx, obj, propval, vp); \
    ret; \
})
#endif

#ifdef JSVAL_TO_DOUBLE_RETURNS_POINTER
# define SE_JSVAL_TO_DOUBLE(v) *JSVAL_TO_DOUBLE(v)
#else
# define SE_JSVAL_TO_DOUBLE(v) JSVAL_TO_DOUBLE(v)
#endif

typedef int (*se_prop_int_cb_t) (void *user_data, char *property, int *num);
typedef void (*se_print_cb_t) (void *user_data, const char *str, ...)
  __attribute__ ((format (printf, 2, 3)));
typedef void (*se_error_cb_t) (const char *, const JSErrorReport *);

typedef struct se_s se_t; /* se: _s_cript _e_ngine */
typedef struct se_o_s se_o_t; /* script engine object */

typedef enum {
  SE_GROUP_HIDDEN,
  SE_GROUP_ENGINE,
  SE_GROUP_DIALOGUE,
  SE_GROUP_PLAYLIST,
  SE_GROUP_FILE,
  SE_GROUP_STATUS,
  SE_GROUP_PROPERTIES,
  SE_GROUP_INPUT,
  SE_GROUP_EXTERNAL,
} se_group_t;

struct se_s {

  JSContext     *cx;
  JSObject      *global;
  se_o_t        *g;
  pthread_mutex_t lock;

  jsval          rval;
  JSString      *str;

  se_print_cb_t  print_cb;
  void          *print_cb_data;

  se_error_cb_t  error_cb;
};

typedef struct {
  char		*id;
  JSFunction	*fun;
  se_group_t	 group;
  const char	*arghelp;
  const char	*funchelp;
} se_f_t;

typedef enum {
  SE_TYPE_STRING,
  SE_TYPE_BOOL,
  SE_TYPE_INT,
  SE_TYPE_DOUBLE,
} se_prop_type_t;

typedef union {
  char *s;
  int i;
  double d;
} se_prop_read_t;

struct se_prop_s;
typedef void (*se_prop_reader_t) (const struct se_prop_s *, se_prop_read_t *);

struct se_prop_s {
  char              *id;
  char              *value;
  se_prop_reader_t   reader;
  GList             *listeners;
  se_prop_type_t     se_type;
  gboolean	     constant;
  intptr_t	     data;
};
typedef struct se_prop_s se_prop_t;

typedef int (*se_prop_cb_t) (void *, se_t *, se_o_t *, se_prop_t *, se_prop_read_t r);

typedef struct {

  se_prop_cb_t   cb;
  void          *user_data;

} se_prop_listener_t;

struct se_o_s {

  JSObject          *obj;

  se_o_t            *parent;
  char              *id;

  GList             *children;

  GList             *functions; /* methods */

  GList             *properties;

  void              *user_data;

  se_group_t	     group;
  const char	    *help;
};

extern se_o_t *event_obj;

/*
 * create a javascript engine with predefined xine functions
 * and objects
 */
se_t *se_new (void);

void se_result_cb (void *data, const char *format, ...)
	__attribute__ ((format (printf, 2, 3)));
void se_error_cb (const char *, const JSErrorReport *);

int se_eval (se_t *se, const gchar *script, se_o_t *obj,
	     se_print_cb_t print_cb, void *print_cb_data, const char *src);
int se_eval_ext (se_t *, const gchar *, se_o_t *, se_print_cb_t, void *,
		 se_error_cb_t, const char *);

gchar *se_result_str (se_t *se);

int se_result_int (se_t *se, JSInt32 *num);
int se_result_double (se_t *se, JSFloat64 *num);
int se_result_bool (se_t *se, JSBool *num);
int se_result_num_as_int (se_t *se, JSInt32 *num);
int se_result_num_as_double (se_t *se, JSFloat64 *num);

/*
 * create a script engine object (in javascript name space)
 */

se_o_t *se_create_object (se_t *se, se_o_t *parent /* may be NULL */,
			  const char *name, void *user_data,
			  se_group_t group, const char *help);

se_o_t *se_find_object (se_t *se, se_o_t *parent, const char *name);
se_o_t *se_get_object (se_t *se, se_o_t *parent, const char *name);
  /* never returns NULL, but don't use if the returned object may be modified */

/*
 * create functions (methods)
 */

typedef struct {
  const char *name;
  JSNative func;
  uintN nargs, attrs;
  se_group_t group;
  const char *arghelp, *funchelp;
} se_f_def_t;

se_f_t *se_defun (se_t *se, se_o_t *o /* may be NULL */,
		  const char *name, JSNative fun, uintN nargs, uintN attrs,
		  se_group_t, const char *arg_help, const char *func_help);
void se_defuns (se_t *se, se_o_t *o /* may be NULL */, const se_f_def_t defs[]);

/*
 * get/set/observe properties (attributes)
 */

/* does nothing if property already exists */
void   se_prop_create (se_t *se, se_o_t *o,
		       const char *id, const char *value,
		       se_prop_type_t, intptr_t data, gboolean constant);
void   se_prop_create_int (se_t *se, se_o_t *o,
			   const char *id, int value, gboolean constant);
void   se_prop_create_double (se_t *se, se_o_t *o,
			      const char *id, double value, gboolean constant);
void   se_prop_create_bool (se_t *se, se_o_t *o,
			    const char *id, int value, gboolean constant);
/* the following two automatically set the reader function */
void   se_prop_create_xine_id (se_t *se, se_o_t *o,
			       const char *id, const char *xine_id);
void   se_prop_create_xine_param (se_t *se, se_o_t *o,
				  const char *id, int param,
				  se_prop_type_t se_type);

void se_prop_create_with_reader (se_t *, se_o_t *, const char *id,
				 const char *value, se_prop_type_t,
				 intptr_t data, gboolean constant,
				 se_prop_reader_t);

void   se_prop_set_reader_func (se_t *se, se_o_t *o,
				const char *id, se_prop_reader_t);

/* creates property if it doesn't exist */
void   se_prop_set_int (se_t *se, se_o_t *o,
			const char *id, int value);
void   se_prop_set_double (se_t *se, se_o_t *o,
			   const char *id, double value);
void   se_prop_set_bool (se_t *se, se_o_t *o,
			 const char *id, int value);
void   se_prop_set (se_t *se, se_o_t *o,
		    const char *id, const char *value);

char *se_prop_get (se_t *se, se_o_t *o, const char *id);
int   se_prop_get_int (se_t *se, se_o_t *o, const char *id);
double se_prop_get_double (se_t *se, se_o_t *o, const char *id);
int   se_prop_get_bool (se_t *se, se_o_t *o, const char *id);

/* add a property observer (callback will be called whenever the
 * properties value changes
 */
void   se_prop_add_listener (se_t *se, se_o_t *o,
			     const char *id,
			     se_prop_cb_t prop_cb,
			     void *user_data);

JSBool se_warn_initialisation (JSContext *, const char *);

#ifdef LOG
#define se_log_fncall(func) printf ("script_engine: %s() called\n", (func))
#define se_log(FMT, ...) printf ("script_engine: " FMT, ## __VA_ARGS__)
#else
#define se_log_fncall(func)
#define se_log(FMT, ...)
#endif

#define se_log_fncall_deprecated(func) \
  do { \
    se_log_fncall ((func)); \
    JS_ReportWarning (cx, _("Deprecated: %s"), func); \
  } while (0)

#define se_log_fncall_obsolete(func) \
  do { \
    se_log_fncall ((func)); \
    JS_ReportWarning (cx, _("Obsolete: %s"), func); \
  } while (0)

#define se_log_fncall_checkinit(func) \
  do \
  { \
    const char *const _func_ = (func); \
    if (!initialised) \
      return se_warn_initialisation (cx, _func_); \
    se_log_fncall (_func_); \
  } while (0)


/* Validation - assumes se_t *se, uintN argc, jsval *argv */

#define se_argc_check(value, FUNC) \
  if (argc != (value)) { \
    JS_ReportError (cx, \
		  _("error: %s() needs %d parameters\n"), (FUNC), (value)); \
    return JS_TRUE; \
  }

#define se_argc_check_range(min, max, FUNC) \
  if (argc < (uintN)(min) || argc > (uintN)(max)) { \
    JS_ReportError (cx, \
	   _("error: %s() needs %d...%d parameters\n"), (FUNC), (min), (max)); \
    return JS_TRUE; \
  }

#define se_argc_check_max(max, FUNC) \
  if (argc > (uintN)(max)) { \
    JS_ReportError (cx, \
		  _("error: %s() needs 0...%d parameters\n"), (FUNC), (max)); \
    return JS_TRUE; \
  }

#define se_arg_is_object(n, FUNC) \
  if (!JSVAL_IS_OBJECT (argv[(n)])) { \
    JS_ReportError (cx, \
		 _("error: %s() argument %d is not an object\n"), (FUNC), (n)+1); \
    return JS_TRUE; \
  }

#define se_arg_is_int(n, FUNC) \
  if (!JSVAL_IS_INT (argv[(n)])) { \
    JS_ReportError (cx, \
		 _("error: %s() argument %d is not an int\n"), (FUNC), (n)+1); \
    return JS_TRUE; \
  }

#define se_arg_is_int_or_bool(n, FUNC) \
  if (!JSVAL_IS_INT (argv[(n)]) && !JSVAL_IS_BOOLEAN (argv[(n)])) { \
    JS_ReportError (cx, \
		 _("error: %s() argument %d is not an int or a boolean\n"), (FUNC), (n)+1); \
    return JS_TRUE; \
  }

#define se_arg_is_number(n, FUNC) \
  if (!JSVAL_IS_NUMBER (argv[(n)])) { \
    JS_ReportError (cx, \
		 _("error: %s() argument %d is not a number\n"), (FUNC), (n)+1); \
    return JS_TRUE; \
  }

#define se_arg_is_string(n, FUNC) \
  if (!JSVAL_IS_STRING (argv[(n)])) { \
    JS_ReportError (cx, \
	       _("error: %s() argument %d is not a string\n"), (FUNC), (n)+1); \
    return JS_TRUE; \
  }

#define se_arg_is_string_or_null(n, FUNC) \
  if (!JSVAL_IS_STRING (argv[(n)]) && !JSVAL_IS_NULL (argv[(n)])) { \
    JS_ReportError (cx, \
	       _("error: %s() argument %d is not a string\n"), (FUNC), (n)+1); \
    return JS_TRUE; \
  }

#endif
