/*
;    Project:       Open Vehicle Monitor System
;    Date:          14th March 2017
;
;    Changes:
;    1.0  Initial release
;
;    (C) 2011       Michael Stegen / Stegen Electronics
;    (C) 2011-2017  Mark Webb-Johnson
;    (C) 2011        Sonny Chen @ EPRO/DX
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/

#include "ovms_log.h"
static const char *TAG = "ovms-duktape";

#include <string>
#include <fstream>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <dirent.h>
#include <esp_task_wdt.h>
#include "ovms_malloc.h"
#include "ovms_module.h"
#include "ovms_duktape.h"
#include "ovms_config.h"
#include "ovms_command.h"
#include "ovms_events.h"
#include "console_async.h"
#include "buffered_shell.h"
#include "ovms_netmanager.h"
#include "ovms_tls.h"

#ifdef CONFIG_OVMS_COMP_PLUGINS
#include "ovms_plugins.h"
#endif // #ifdef CONFIG_OVMS_COMP_PLUGINS

OvmsDuktape MyDuktape __attribute__ ((init_priority (1000)));
OvmsWriter* duktapewriter = NULL;

#ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
  #include "umm_malloc.c"
  static void *umm_memory = NULL;
#endif

////////////////////////////////////////////////////////////////////////////////
// Duktape utility functions

static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata);
static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main);

static duk_bool_t duk__get_cached_module(duk_context *ctx, const char *id)
  {
	duk_push_global_stash(ctx);
	(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
	if (duk_get_prop_string(ctx, -1, id))
    {
		duk_remove(ctx, -2);
		duk_remove(ctx, -2);
		return 1;
  	}
  else
    {
		duk_pop_3(ctx);
		return 0;
	  }
  }

/* Place a `module` object on the top of the value stack into the require cache
 * based on its `.id` property.  As a convenience to the caller, leave the
 * object on top of the value stack afterwards.
 */
static void duk__put_cached_module(duk_context *ctx)
  {
	/* [ ... module ] */

	duk_push_global_stash(ctx);
	(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
	duk_dup(ctx, -3);

	/* [ ... module stash req_cache module ] */

	(void) duk_get_prop_string(ctx, -1, "id");
	duk_dup(ctx, -2);
	duk_put_prop(ctx, -4);

	duk_pop_3(ctx);  /* [ ... module ] */
  }

static void duk__del_cached_module(duk_context *ctx, const char *id)
  {
	duk_push_global_stash(ctx);
	(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
	duk_del_prop_string(ctx, -1, id);
	duk_pop_2(ctx);
  }

static duk_ret_t duk__handle_require(duk_context *ctx)
  {
	/*
	 *  Value stack handling here is a bit sloppy but should be correct.
	 *  Call handling will clean up any extra garbage for us.
	 */

	const char *id;
	const char *parent_id;
	duk_idx_t module_idx;
	duk_idx_t stash_idx;
	duk_int_t ret;

	duk_push_global_stash(ctx);
	stash_idx = duk_normalize_index(ctx, -1);

	duk_push_current_function(ctx);
	(void) duk_get_prop_string(ctx, -1, "\xff" "moduleId");
	parent_id = duk_require_string(ctx, -1);
	(void) parent_id;  /* not used directly; suppress warning */

	/* [ id stash require parent_id ] */

	id = duk_require_string(ctx, 0);

	(void) duk_get_prop_string(ctx, stash_idx, "\xff" "modResolve");
	duk_dup(ctx, 0);   /* module ID */
	duk_dup(ctx, -3);  /* parent ID */
	duk_call(ctx, 2);

	/* [ ... stash ... resolved_id ] */

	id = duk_require_string(ctx, -1);

	if (duk__get_cached_module(ctx, id))
    {
		goto have_module;  /* use the cached module */
	  }

	duk__push_module_object(ctx, id, 0 /*main*/);
	duk__put_cached_module(ctx);  /* module remains on stack */

	/*
	 *  From here on out, we have to be careful not to throw.  If it can't be
	 *  avoided, the error must be caught and the module removed from the
	 *  require cache before rethrowing.  This allows the application to
	 *  reattempt loading the module.
	 */

	module_idx = duk_normalize_index(ctx, -1);

	/* [ ... stash ... resolved_id module ] */

	(void) duk_get_prop_string(ctx, stash_idx, "\xff" "modLoad");
	duk_dup(ctx, -3);  /* resolved ID */
	(void) duk_get_prop_string(ctx, module_idx, "exports");
	duk_dup(ctx, module_idx);
	ret = duk_pcall(ctx, 3);
	if (ret != DUK_EXEC_SUCCESS)
    {
		duk__del_cached_module(ctx, id);
		duk_throw(ctx);  /* rethrow */
	  }

	if (duk_is_string(ctx, -1))
    {
		duk_int_t ret;

		/* [ ... module source ] */
		ret = duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
		if (ret != DUK_EXEC_SUCCESS)
      {
			duk__del_cached_module(ctx, id);
			duk_throw(ctx);  /* rethrow */
		  }
	  }
  else if (duk_is_undefined(ctx, -1))
    {
		duk_pop(ctx);
  	}
  else
    {
		duk__del_cached_module(ctx, id);
		duk_error(ctx, DUK_ERR_TYPE_ERROR, "invalid module load callback return value");
	  }

	/* fall through */

 have_module:
	/* [ ... module ] */

	(void) duk_get_prop_string(ctx, -1, "exports");
	return 1;
  }

static void duk__push_require_function(duk_context *ctx, const char *id)
  {
	duk_push_c_function(ctx, duk__handle_require, 1);
	duk_push_string(ctx, "name");
	duk_push_string(ctx, "require");
	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE);
	duk_push_string(ctx, id);
	duk_put_prop_string(ctx, -2, "\xff" "moduleId");

	/* require.cache */
	duk_push_global_stash(ctx);
	(void) duk_get_prop_string(ctx, -1, "\xff" "requireCache");
	duk_put_prop_string(ctx, -3, "cache");
	duk_pop(ctx);

	/* require.main */
	duk_push_global_stash(ctx);
	(void) duk_get_prop_string(ctx, -1, "\xff" "mainModule");
	duk_put_prop_string(ctx, -3, "main");
	duk_pop(ctx);
  }

static void duk__push_module_object(duk_context *ctx, const char *id, duk_bool_t main)
  {
	duk_push_object(ctx);

	/* Set this as the main module, if requested */
	if (main)
    {
		duk_push_global_stash(ctx);
		duk_dup(ctx, -2);
		duk_put_prop_string(ctx, -2, "\xff" "mainModule");
		duk_pop(ctx);
	  }

	/* Node.js uses the canonicalized filename of a module for both module.id
	 * and module.filename.  We have no concept of a file system here, so just
	 * use the module ID for both values.
	 */
	duk_push_string(ctx, id);
	duk_dup(ctx, -1);
	duk_put_prop_string(ctx, -3, "filename");
	duk_put_prop_string(ctx, -2, "id");

	/* module.exports = {} */
	duk_push_object(ctx);
	duk_put_prop_string(ctx, -2, "exports");

	/* module.loaded = false */
	duk_push_false(ctx);
	duk_put_prop_string(ctx, -2, "loaded");

	/* module.require */
	duk__push_require_function(ctx, id);
	duk_put_prop_string(ctx, -2, "require");
  }

static duk_int_t duk__eval_module_source(duk_context *ctx, void *udata)
  {
	const char *src;

	/*
	 *  Stack: [ ... module source ]
	 */

	(void) udata;

	/* Wrap the module code in a function expression.  This is the simplest
	 * way to implement CommonJS closure semantics and matches the behavior of
	 * e.g. Node.js.
	 */
	duk_push_string(ctx, "(function(exports,require,module,__filename,__dirname){");
	src = duk_require_string(ctx, -2);
	duk_push_string(ctx, (src[0] == '#' && src[1] == '!') ? "//" : "");  /* Shebang support. */
	duk_dup(ctx, -3);  /* source */
	duk_push_string(ctx, "\n})");  /* Newline allows module last line to contain a // comment. */
	duk_concat(ctx, 4);

	/* [ ... module source func_src ] */

	(void) duk_get_prop_string(ctx, -3, "filename");
	duk_compile(ctx, DUK_COMPILE_EVAL);
	duk_call(ctx, 0);

	/* [ ... module source func ] */

	/* Set name for the wrapper function. */
	duk_push_string(ctx, "name");
	duk_push_string(ctx, "main");
	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);

	/* call the function wrapper */
	(void) duk_get_prop_string(ctx, -3, "exports");   /* exports */
	(void) duk_get_prop_string(ctx, -4, "require");   /* require */
	duk_dup(ctx, -5);                                 /* module */
	(void) duk_get_prop_string(ctx, -6, "filename");  /* __filename */
	duk_push_undefined(ctx);                          /* __dirname */
	duk_call(ctx, 5);

	/* [ ... module source result(ignore) ] */

	/* module.loaded = true */
	duk_push_true(ctx);
	duk_put_prop_string(ctx, -4, "loaded");

	/* [ ... module source retval ] */

	duk_pop_2(ctx);

	/* [ ... module ] */

	return 1;
  }

/* Load a module as the 'main' module. */
duk_ret_t duk_module_node_peval_main(duk_context *ctx, const char *path)
  {
	/*
	 *  Stack: [ ... source ]
	 */

	duk__push_module_object(ctx, path, 1 /*main*/);
	/* [ ... source module ] */

	duk_dup(ctx, 0);
	/* [ ... source module source ] */

	return duk_safe_call(ctx, duk__eval_module_source, NULL, 2, 1);
  }

void duk_module_node_init(duk_context *ctx)
  {
	/*
	 *  Stack: [ ... options ] => [ ... ]
	 */

	duk_idx_t options_idx;

	duk_require_object_coercible(ctx, -1);  /* error before setting up requireCache */
	options_idx = duk_require_normalize_index(ctx, -1);

	/* Initialize the require cache to a fresh object. */
	duk_push_global_stash(ctx);
	duk_push_bare_object(ctx);
	duk_put_prop_string(ctx, -2, "\xff" "requireCache");
	duk_pop(ctx);

	/* Stash callbacks for later use.  User code can overwrite them later
	 * on directly by accessing the global stash.
	 */
	duk_push_global_stash(ctx);
	duk_get_prop_string(ctx, options_idx, "resolve");
	duk_require_function(ctx, -1);
	duk_put_prop_string(ctx, -2, "\xff" "modResolve");
	duk_get_prop_string(ctx, options_idx, "load");
	duk_require_function(ctx, -1);
	duk_put_prop_string(ctx, -2, "\xff" "modLoad");
	duk_pop(ctx);

	/* Stash main module. */
	duk_push_global_stash(ctx);
	duk_push_undefined(ctx);
	duk_put_prop_string(ctx, -2, "\xff" "mainModule");
	duk_pop(ctx);

	/* register `require` as a global function. */
	duk_push_global_object(ctx);
	duk_push_string(ctx, "require");
	duk__push_require_function(ctx, "");
	duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE |
	                      DUK_DEFPROP_SET_WRITABLE |
	                      DUK_DEFPROP_SET_CONFIGURABLE);
	duk_pop(ctx);

	duk_pop(ctx);  /* pop argument */
  }

////////////////////////////////////////////////////////////////////////////////
// Duktape engine task

void DukTapeLaunchTask(void *pvParameters)
  {
  OvmsDuktape* me = (OvmsDuktape*)pvParameters;

  me->DukTapeTask();
  }

void* DukOvmsAlloc(void *udata, duk_size_t size)
  {
  #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
    return umm_malloc(size);
  #else
    return ExternalRamMalloc(size);
  #endif
  }

void* DukOvmsRealloc(void *udata, void *ptr, duk_size_t size)
  {
  #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
    return umm_realloc(ptr, size);
  #else
    return ExternalRamRealloc(ptr, size);
  #endif
  }

void DukOvmsFree(void *udata, void *ptr)
  {
  #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
    umm_free(ptr);
  #else
    free(ptr);
  #endif
  }

void DukOvmsFatalHandler(void *udata, const char *msg)
  {
  ESP_LOGE(TAG, "Duktape fatal error: %s",msg);
  abort();
  }

void DukOvmsErrorHandler(duk_context *ctx, duk_idx_t err_idx, OvmsWriter *writer, const char *filename)
  {
  const char *error, *stack = NULL;
  int linenumber = 0;
  if (!filename) filename = "eval";

  duk_require_stack(ctx, 1);

  if (duk_is_error(ctx, err_idx))
    {
    duk_get_prop_string(ctx, err_idx, "fileName");
    if (duk_is_string(ctx, -1))
      filename = duk_get_string(ctx, -1);
    duk_pop(ctx);
    duk_get_prop_string(ctx, err_idx, "lineNumber");
    linenumber = duk_get_number_default(ctx, -1, 0);
    duk_pop(ctx);
    duk_get_prop_string(ctx, err_idx, "stack");
    if (duk_is_string(ctx, -1))
      stack = duk_get_string(ctx, -1);
    duk_pop(ctx);
    }

  error = stack ? stack : duk_safe_to_string(ctx, err_idx);

  if (writer)
    {
    // some errors have line numbers
    if (linenumber == 0 || strstr(error, "(line "))
      writer->printf("ERROR: %s\n", error);
    else
      writer->printf("ERROR: %s (line %d)\n", error, linenumber);
    }
  else
    {
    ESP_LOGE(TAG, "[%s:%d] %s", filename, linenumber, error);
    }
  }

static duk_ret_t DukOvmsResolveModule(duk_context *ctx)
  {
  const char *module_id;
  const char *parent_id;

  module_id = duk_require_string(ctx, 0);
  parent_id = duk_require_string(ctx, 1);

  duk_push_sprintf(ctx, "%s.js", module_id);
  ESP_LOGD(TAG,"resolve_cb: id:'%s', parent-id:'%s', resolve-to:'%s'",
                  module_id, parent_id, duk_get_string(ctx, -1));
  return 1;
  }

static duk_ret_t DukOvmsLoadModule(duk_context *ctx)
  {
  const char *filename;
  const char *module_id;

  module_id = duk_require_string(ctx, 0);
  duk_get_prop_string(ctx, 2, "filename");
  filename = duk_require_string(ctx, -1);

  if (strncmp(module_id,"int/",4)==0)
    {
    // Load internal module
    std::string name(module_id+4,strlen(module_id)-7);
    duktape_registermodule_t* mod = MyDuktape.FindDuktapeModule(name.c_str());
    if (mod == NULL)
      {
      duk_error(ctx, DUK_ERR_TYPE_ERROR, "load_cb: cannot find internal module: %s", module_id);
      return 0;
      }
    else
      {
      ESP_LOGD(TAG,"load_cb: id:'%s' internally provided %s (%d bytes)", module_id, filename, mod->length);
      duk_push_lstring(ctx, mod->start, mod->length);
      MyDuktape.NotifyDuktapeModuleLoad(filename);
      return 1;
      }
    }

  ESP_LOGD(TAG,"load_cb: id:'%s', filename:'%s'", module_id, filename);

  // Resolve the module path
  std::string path;
  FILE* sf;
  if (strncmp(filename,"plugin/",7)==0)
    {
    path = std::string("/store/plugins/");
    path.append(filename+7);
    sf = fopen(path.c_str(), "r");
    }
  else
    {
    path = std::string("/store/scripts/");
    path.append(filename);
    sf = fopen(path.c_str(), "r");
    if (sf == NULL)
      {
      path = std::string("/sd/scripts/");
      path.append(filename);
      sf = fopen(path.c_str(), "r");
      }
    }

  // Go ahead and load the module
  if (sf == NULL)
    {
    duk_error(ctx, DUK_ERR_TYPE_ERROR, "load_cb: cannot find module: %s", module_id);
    return 0;
    }
  else
    {
    fseek(sf,0,SEEK_END);
    long slen = ftell(sf);
    fseek(sf,0,SEEK_SET);
    char *script = new char[slen+1];
    memset(script,0,slen+1);
    fread(script,1,slen,sf);
    duk_push_string(ctx, script);
    delete [] script;
    fclose(sf);
    ESP_LOGD(TAG,"load_cb: id:'%s' vfs provided %s (%lu bytes)", module_id, filename, slen);
    MyDuktape.NotifyDuktapeModuleLoad(filename);
    }

  return 1;
  }

////////////////////////////////////////////////////////////////////////////////
// DuktapeObject

/**
 * Construct empty (uncoupled) DuktapeObject
 */
DuktapeObject::DuktapeObject()
  {
  }

/**
 * Construct DuktapeObject coupled to stack object
 *  Stack: unchanged
 */
DuktapeObject::DuktapeObject(duk_context *ctx, int obj_idx)
  {
  Couple(ctx, obj_idx);
  assert(IsCoupled());
  }

/**
 * Destruct DuktapeObject
 *  Note: no JS context here, override Finalize() for JS destructor
 */
DuktapeObject::~DuktapeObject()
  {
  }

/**
 * Ref: increment reference count
 */
void DuktapeObject::Ref()
  {
  Lock();
  m_refcnt++;
  // ESP_LOGD(TAG, "DuktapeObject::Ref cnt=%d", m_refcnt);
  Unlock();
  }

/**
 * Unref: decrement reference count, delete self if no references left
 *  Returns true if instance has been deleted
 */
bool DuktapeObject::Unref()
  {
  Lock();
  assert(m_refcnt > 0);
  m_refcnt--;
  // ESP_LOGD(TAG, "DuktapeObject::Unref cnt=%d", m_refcnt);
  if (m_refcnt == 0)
    {
    delete this;
    return true;
    }
  Unlock();
  return false;
  }

/**
 * Couple: set hidden instance pointer & finalizer on JS object
 *  Stack: unchanged
 */
bool DuktapeObject::Couple(duk_context *ctx, int obj_idx)
  {
  OvmsRecMutexLock lock(&m_mutex);
  duk_require_object(ctx, obj_idx);
  m_object = duk_get_heapptr(ctx, obj_idx);
  if (!m_object) return false;
  duk_require_stack(ctx, 3);
  duk_push_heapptr(ctx, m_object);
  // set instance pointer:
  duk_push_pointer(ctx, this);
  duk_put_prop_string(ctx, -2, DUK_HIDDEN_SYMBOL("duktapeObject"));
  // set finalizer:
  duk_push_c_function(ctx, DuktapeFinalizer, 2 /*(object,heapDestruct)*/);
  duk_set_finalizer(ctx, -2);
  duk_pop(ctx);
  Ref();
  return true;
  }

/**
 * Decouple: clear hidden instance pointer & finalizer on JS object
 *  Stack: unchanged
 */
void DuktapeObject::Decouple(duk_context *ctx)
  {
    {
    OvmsRecMutexLock lock(&m_mutex);
    if (!m_object) return;
    duk_require_stack(ctx, 3);
    duk_push_heapptr(ctx, m_object);
    // clear instance pointer:
    duk_del_prop_string(ctx, -1, DUK_HIDDEN_SYMBOL("duktapeObject"));
    // clear finalizer:
    duk_push_undefined(ctx);
    duk_set_finalizer(ctx, -2);
    duk_pop(ctx);
    m_object = NULL;
    }
  Unref();
  }

/**
 * GetInstance: get hidden instance pointer from JS object
 *  Returns null if no instance is attached
 *  Stack: unchanged
 */
DuktapeObject* DuktapeObject::GetInstance(duk_context *ctx, int obj_idx)
  {
  DuktapeObject* instance = NULL;
  duk_require_stack(ctx, 2);
  if (duk_get_prop_string(ctx, obj_idx, DUK_HIDDEN_SYMBOL("duktapeObject")))
    instance = (DuktapeObject*)duk_get_pointer(ctx, -1);
  duk_pop(ctx);
  return instance;
  }

/**
 * DuktapeFinalizer: Duktape finalizer (destructor) entry
 */
duk_ret_t DuktapeObject::DuktapeFinalizer(duk_context *ctx)
  {
  DuktapeObject* me = GetInstance(ctx, 0);
  bool heapDestruct = duk_opt_boolean(ctx, 1, false);
  if (me) me->Finalize(ctx, heapDestruct);
  return 0;
  }

/**
 * Finalize: JS destructor, called by garbage collection
 */
void DuktapeObject::Finalize(duk_context *ctx, bool heapDestruct)
  {
  // ESP_LOGD(TAG, "DuktapeObject::Finalize heapDestruct=%d", heapDestruct);
  Decouple(ctx); // deletes self if no reference left
  }

/**
 * Push: push coupled JS object onto stack
 *  Stack: [ … ] → [ … obj ]
 */
duk_idx_t DuktapeObject::Push(duk_context *ctx)
  {
  OvmsRecMutexLock lock(&m_mutex);
  if (!m_object) return DUK_INVALID_INDEX;
  duk_require_stack(ctx, 1);
  return duk_push_heapptr(ctx, m_object);
  }

/**
 * Register object with the global stash to prevent garbage collection
 *  Stack: unchanged
 */
void DuktapeObject::Register(duk_context *ctx)
  {
  OvmsRecMutexLock lock(&m_mutex);
  if (!m_object || m_registered) return;
  // ESP_LOGD(TAG, "DuktapeObject::Register @globalStash");
  duk_require_stack(ctx, 3);

  // get registry:
  duk_push_global_stash(ctx);
  if (!duk_get_prop_string(ctx, -1, "duktapeObjectRegistry"))
    {
    // create registry:
    duk_pop(ctx);
    duk_push_object(ctx);
    duk_dup(ctx, -1);
    duk_put_prop_string(ctx, -3, "duktapeObjectRegistry");
    }

  // register using instance address as key:
  duk_push_pointer(ctx, this);
  duk_push_heapptr(ctx, m_object);
  duk_put_prop(ctx, -3);

  duk_pop_2(ctx); // registry & global stash

  m_registry = NULL;
  m_registered = true;
  }

/**
 * Register with a specific registry object on the stack
 *  Note: you need to ensure the object is stable until deregistration!
 *  Stack: unchanged
 */
void DuktapeObject::Register(duk_context *ctx, int obj_idx)
  {
  OvmsRecMutexLock lock(&m_mutex);
  if (!m_object || m_registered) return;
  // ESP_LOGD(TAG, "DuktapeObject::Register @object");
  duk_require_object(ctx, obj_idx);
  duk_require_stack(ctx, 3);

  // register using instance address as key:
  duk_dup(ctx, obj_idx);
  duk_push_pointer(ctx, this);
  duk_push_heapptr(ctx, m_object);
  duk_put_prop(ctx, -3);
  duk_pop(ctx);

  m_registry = duk_get_heapptr(ctx, obj_idx);
  m_registered = true;
  }

/**
 * Deregister:
 *  Stack: unchanged
 */
void DuktapeObject::Deregister(duk_context *ctx)
  {
  OvmsRecMutexLock lock(&m_mutex);
  if (!m_object || !m_registered) return;
  // ESP_LOGD(TAG, "DuktapeObject::Deregister");
  duk_require_stack(ctx, 3);

  if (m_registry)
    {
    duk_push_heapptr(ctx, m_registry);
    duk_push_pointer(ctx, this);
    duk_del_prop(ctx, -2);
    duk_pop(ctx); // registry
    }
  else
    {
    duk_push_global_stash(ctx);
    if (duk_get_prop_string(ctx, -1, "duktapeObjectRegistry"))
      {
      duk_push_pointer(ctx, this);
      duk_del_prop(ctx, -2);
      }
    duk_pop_2(ctx); // registry & global stash
    }

  m_registry = NULL;
  m_registered = false;
  }

/**
 * RequestCallback: request object method call by Duktape context
 */
void DuktapeObject::RequestCallback(const char* method, void* data /*=NULL*/)
  {
  if (MyDuktape.DukTapeAvailable())
    {
    Ref();
    MyDuktape.DuktapeRequestCallback(this, method, data);
    }
  else
    {
    ESP_LOGE(TAG, "DuktapeObject::RequestCallback(%s) failed: Duktape not available", method);
    }
  }

/**
 * DuktapeCallback: call object method in Duktape context
 */
duk_ret_t DuktapeObject::DuktapeCallback(duk_context *ctx, duktape_queue_t &msg)
  {
  duk_ret_t res = -1;
  if (ctx)
    res = CallMethod(ctx, msg.body.dt_callback.method, msg.body.dt_callback.data);
  else
    ESP_LOGE(TAG, "DuktapeObject::DuktapeCallback(%s) failed: Duktape shutdown", msg.body.dt_callback.method);
  Unref();
  return res;
  }

/**
 * CallMethod: execute object method in Duktape context
 *  Default implementation: no args, no return values, no error processing
 */
duk_ret_t DuktapeObject::CallMethod(duk_context *ctx, const char* method, void* data /*=NULL*/)
  {
  if (!ctx)
    {
    RequestCallback(method, data);
    return 0;
    }
  OvmsRecMutexLock lock(&m_mutex);
  if (!IsCoupled()) return 0;
  int entry_top = duk_get_top(ctx);
  Push(ctx);
  duk_push_string(ctx, method);
  if (duk_pcall_prop(ctx, -2, 0) != 0)
    {
    DukOvmsErrorHandler(ctx, -1);
    }
  // discard return values if any + this:
  duk_pop_n(ctx, duk_get_top(ctx) - entry_top);
  return 0;
  }

////////////////////////////////////////////////////////////////////////////////
// DuktapeObjectRegistration
//
// An object to record a Duktape object registration for OVMS

DuktapeObjectRegistration::DuktapeObjectRegistration(const char* name)
  {
  m_name = name;
  }

DuktapeObjectRegistration::~DuktapeObjectRegistration()
  {
  }

const char* DuktapeObjectRegistration::GetName()
  {
  return m_name;
  }

void DuktapeObjectRegistration::RegisterDuktapeFunction(duk_c_function func, duk_idx_t nargs, const char* name)
  {
  duktape_registerfunction_t* fn = new duktape_registerfunction_t;
  fn->func = func;
  fn->nargs = nargs;
  m_fnmap[name] = fn;
  }

void DuktapeObjectRegistration::RegisterWithDuktape(duk_context* ctx)
  {
  duk_push_object(ctx);

  DuktapeFunctionMap::iterator itm=m_fnmap.begin();
  while (itm!=m_fnmap.end())
    {
    const char* name = itm->first;
    duktape_registerfunction_t* fn = itm->second;
    ESP_LOGD(TAG,"Duktape: Pre-Registered object %s function %s",m_name,name);
    duk_push_c_function(ctx, fn->func, fn->nargs);
    duk_push_string(ctx, "name");
    duk_push_string(ctx, name);
    duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE);  /* Improve stacktraces by displaying function name */
    duk_put_prop_string(ctx, -2, name);
    ++itm;
    }

  duk_put_global_string(ctx, m_name);
  }

////////////////////////////////////////////////////////////////////////////////
// DuktapeConsoleCommand

DuktapeConsoleCommand::DuktapeConsoleCommand(duk_context *ctx, int obj_idx, OvmsCommand* cmd, const char* module)
  : DuktapeObject(ctx, obj_idx)
  {
  m_cmd = cmd;
  m_module = std::string(module);
  }

DuktapeConsoleCommand::~DuktapeConsoleCommand()
  {
  }

////////////////////////////////////////////////////////////////////////////////
// OvmsDuktape
//
// This is the main OVMS Duktape registry and singleton utiltiy object

OvmsDuktape::OvmsDuktape()
  {
  ESP_LOGI(TAG, "Initialising DUKTAPE Registry (1000)");

  m_dukctx = NULL;
  m_duktaskid = NULL;
  m_duktaskqueue = NULL;

  // Register standard modules...
  extern const char mod_pubsub_js_start[]     asm("_binary_pubsub_js_start");
  extern const char mod_pubsub_js_end[]       asm("_binary_pubsub_js_end");
  RegisterDuktapeModule(mod_pubsub_js_start, mod_pubsub_js_end - mod_pubsub_js_start, "PubSub");

  extern const char mod_json_js_start[]     asm("_binary_json_js_start");
  extern const char mod_json_js_end[]       asm("_binary_json_js_end");
  RegisterDuktapeModule(mod_json_js_start, mod_json_js_end - mod_json_js_start, "JSON");

  // Start the DukTape task...
  m_duktaskqueue = xQueueCreate(CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_QUEUE_SIZE,sizeof(duktape_queue_t));
  xTaskCreatePinnedToCore(DukTapeLaunchTask, "OVMS DukTape",
                          CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_STACK, (void*)this,
                          CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_PRIORITY, &m_duktaskid, CORE(1));
  AddTaskToMap(m_duktaskid);
  }

OvmsDuktape::~OvmsDuktape()
  {
  duk_destroy_heap(m_dukctx);
  #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
    if (umm_memory != NULL)
      {
      free(umm_memory);
      umm_memory = NULL;
      }
  #endif
  m_dukctx = NULL;
  }

void OvmsDuktape::RegisterDuktapeFunction(duk_c_function func, duk_idx_t nargs, const char* name)
  {
  duktape_registerfunction_t* fn = new duktape_registerfunction_t;
  fn->func = func;
  fn->nargs = nargs;
  m_fnmap[name] = fn;
  }

void OvmsDuktape::RegisterDuktapeModule(const char* start, size_t length, const char* name)
  {
  duktape_registermodule_t* mn = new duktape_registermodule_t;
  mn->start = start;
  mn->length = length;
  m_modmap[name] = mn;
  }

duktape_registermodule_t* OvmsDuktape::FindDuktapeModule(const char* name)
  {
  auto mod = m_modmap.find(name);
  if (mod == m_modmap.end())
    { return NULL; }
  else
    { return mod->second; }
  }

void OvmsDuktape::RegisterDuktapeObject(DuktapeObjectRegistration* ob)
  {
  m_obmap[ob->GetName()] = ob;
  }

void OvmsDuktape::AutoInitDuktape()
  {
  if (MyConfig.GetParamValueBool("auto", "scripting", true))
    {
    duktape_queue_t dmsg;
    memset(&dmsg, 0, sizeof(dmsg));
    dmsg.type = DUKTAPE_autoinit;
    DuktapeDispatch(&dmsg);
    }
  }

void OvmsDuktape::EventScript(std::string event, void* data)
  {
  if (!m_dukctx) return;

  // dispatch event to PubSub component:
  duktape_queue_t dmsg;
  memset(&dmsg, 0, sizeof(dmsg));
  dmsg.type = DUKTAPE_event;
  dmsg.body.dt_event.name = strdup(event.c_str());
  dmsg.body.dt_event.data = NULL; // data unused, may also be invalid in async script execution
  if (!DuktapeDispatch(&dmsg, 0))
    {
    ESP_LOGE(TAG, "EventScript: event '%s' lost (queue overflow)", event.c_str());
    free((void*)dmsg.body.dt_event.name);
    }
  else
   {
   // event processing delayed?
   int qwait = uxQueueMessagesWaiting(m_duktaskqueue);
   if (qwait > 10)
     {
     ESP_LOGW(TAG, "EventScript: event '%s' delayed, queued at position %d/%d", event.c_str(),
       qwait, CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_QUEUE_SIZE);
     }
   }

  if (event == "ticker.60")
    {
    // request garbage collection once per minute:
    DuktapeCompact(false);
    }
  }

bool OvmsDuktape::DuktapeDispatch(duktape_queue_t* msg, TickType_t queuewait /*=portMAX_DELAY*/)
  {
  msg->waitcompletion = NULL;
  if (xQueueSend(m_duktaskqueue, msg, queuewait) != pdPASS)
    {
    ESP_LOGW(TAG, "DuktapeDispatch: msg type %u lost, queue full", msg->type);
    return false;
    }
  else
    {
    return true;
    }
  }

void OvmsDuktape::DuktapeDispatchWait(duktape_queue_t* msg)
  {
  msg->waitcompletion = xSemaphoreCreateBinary();
  xQueueSend(m_duktaskqueue, msg, portMAX_DELAY);
  xSemaphoreTake(msg->waitcompletion, portMAX_DELAY);
  vSemaphoreDelete(msg->waitcompletion);
  msg->waitcompletion = NULL;
  }

void OvmsDuktape::DuktapeEvalNoResult(const char* text, OvmsWriter* writer, const char* filename /*=NULL*/)
  {
  duktape_queue_t dmsg;
  memset(&dmsg, 0, sizeof(dmsg));
  dmsg.type = DUKTAPE_evalnoresult;
  dmsg.writer = writer;
  dmsg.body.dt_evalnoresult.text = text;
  dmsg.body.dt_evalnoresult.filename = filename;
  DuktapeDispatchWait(&dmsg);
  }

float OvmsDuktape::DuktapeEvalFloatResult(const char* text, OvmsWriter* writer)
  {
  float result = 0;
  duktape_queue_t dmsg;
  memset(&dmsg, 0, sizeof(dmsg));
  dmsg.type = DUKTAPE_evalfloatresult;
  dmsg.writer = writer;
  dmsg.body.dt_evalfloatresult.text = text;
  dmsg.body.dt_evalfloatresult.result = &result;
  DuktapeDispatchWait(&dmsg);
  return result;
  }

int OvmsDuktape::DuktapeEvalIntResult(const char* text, OvmsWriter* writer)
  {
  int result = 0;
  duktape_queue_t dmsg;
  memset(&dmsg, 0, sizeof(dmsg));
  dmsg.type = DUKTAPE_evalintresult;
  dmsg.writer = writer;
  dmsg.body.dt_evalintresult.text = text;
  dmsg.body.dt_evalintresult.result = &result;
  DuktapeDispatchWait(&dmsg);
  return result;
  }

void OvmsDuktape::DuktapeReload()
  {
  duktape_queue_t dmsg;
  memset(&dmsg, 0, sizeof(dmsg));
  dmsg.type = DUKTAPE_reload;
  DuktapeDispatchWait(&dmsg);
  }

void OvmsDuktape::DuktapeCompact(bool wait /*=true*/)
  {
  duktape_queue_t dmsg;
  memset(&dmsg, 0, sizeof(dmsg));
  dmsg.type = DUKTAPE_compact;
  if (wait)
    DuktapeDispatchWait(&dmsg);
  else
    DuktapeDispatch(&dmsg, 0);
  }

void OvmsDuktape::DuktapeRequestCallback(DuktapeObject* instance, const char* method, void* data)
  {
  duktape_queue_t dmsg;
  memset(&dmsg, 0, sizeof(dmsg));
  dmsg.type = DUKTAPE_callback;
  dmsg.body.dt_callback.instance = instance;
  dmsg.body.dt_callback.method = method;
  dmsg.body.dt_callback.data = data;
  DuktapeDispatch(&dmsg);
  }

OvmsWriter* OvmsDuktape::GetDuktapeWriter()
  {
  return duktapewriter;
  }

void OvmsDuktape::DukGetCallInfo(duk_context *ctx, std::string *filename, int *linenumber, std::string *function)
  {
  duk_require_stack(ctx, 3);
  for (int i=-2; ; i--)
    {
    *filename = "";
    *function = "";
    duk_inspect_callstack_entry(ctx, i);
    if (duk_is_undefined(ctx, -1))
      {
      duk_pop(ctx);
      break;
      }
    duk_get_prop_string(ctx, -1, "lineNumber");
    *linenumber = duk_get_number_default(ctx, -1, 0);
    duk_pop(ctx);
    if (duk_get_prop_string(ctx, -1, "function"))
      {
      duk_get_prop_string(ctx, -1, "fileName");
      *filename = duk_get_string_default(ctx, -1, "");
      duk_pop(ctx);
      duk_get_prop_string(ctx, -1, "name");
      *function = duk_get_string_default(ctx, -1, "");
      duk_pop(ctx);
      }
    duk_pop_2(ctx);
    // skip internal modules:
    if (!startsWith(*filename, "int/"))
      break;
    }
  }

void OvmsDuktape::NotifyDuktapeModuleLoad(const char* filename)
  {
  ESP_LOGD(TAG,"Duktape: module load: %s",filename);

  // We don't need to do anythign special with this
  }

void OvmsDuktape::NotifyDuktapeModuleUnload(const char* filename)
  {
  ESP_LOGD(TAG,"Duktape: module unload: %s",filename);

  // We need to go through the registered duktape commands and
  // unregister any belonging to the specified module
  for (auto it = m_cmdmap.begin(); it != m_cmdmap.end(); ++it)
    {
    OvmsCommand* cmd = it->first;
    DuktapeConsoleCommand* dcc = it->second;
    if (dcc->m_module.compare(filename) == 0)
      {
      ESP_LOGD(TAG,"Duktape: unregister command %s/%s",
        cmd->GetParent()->GetName(), cmd->GetName());
      delete dcc;
      cmd->GetParent()->UnregisterCommand(cmd->GetName());
      delete cmd;
      m_cmdmap.erase(it);
      }
    }
  }

void OvmsDuktape::NotifyDuktapeModuleUnloadAll()
  {
  ESP_LOGD(TAG,"Duktape: module unload all");

  // We need to unregister all registered duktape commands
  // (for all modules)
  for (auto it = m_cmdmap.begin(); it != m_cmdmap.end(); ++it)
    {
    OvmsCommand* cmd = it->first;
    DuktapeConsoleCommand* dcc = it->second;
    ESP_LOGD(TAG,"Duktape: unregister command %s/%s",
      cmd->GetParent()->GetName(), cmd->GetName());
    delete dcc;
    cmd->GetParent()->UnregisterCommand(cmd->GetName());
    delete cmd;
    }
  m_cmdmap.clear();
  }

void DukOvmsCommandRegisterRun(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
  {
  ESP_LOGD(TAG, "DukOvmsCommandRegisterRun(%s)",cmd->GetName());

  auto it = MyDuktape.m_cmdmap.find(cmd);
  if (it == MyDuktape.m_cmdmap.end())
    {
    ESP_LOGE(TAG, "Command '%s' cannot be found in registry",cmd->GetName());
    return;
    }
  else
    {
    // TODO
    // DuktapeConsoleCommand* dcc = it->second;
    // Perform the callback
    }
  }

bool OvmsDuktape::RegisterDuktapeConsoleCommand(
  duk_context *ctx, int obj_idx,
  const char* filename,
  const char* parent,
  const char* name, const char* title,
  const char *usage, int min, int max)
  {
  ESP_LOGD(TAG,"Duktape: register console command '%s'",name);

  // We need to register the specified console command, to
  // the specified module

  OvmsCommand* cmd;
  if (*parent == 0)
    {
    cmd = MyCommandApp.RegisterCommand(
      name, title, DukOvmsCommandRegisterRun, usage, min, max);
    }
  else
    {
    OvmsCommand* pcmd = MyCommandApp.FindCommandFullName(parent);
    if (pcmd == NULL)
      {
      ESP_LOGE(TAG,"Duktape: Script %s trying to register unknown command %s/%s",
          filename, parent, name);
      return false;
      }
    cmd = pcmd->RegisterCommand(
      name, title, DukOvmsCommandRegisterRun, usage, min, max);
    }

  if (cmd != NULL)
    {
    DuktapeConsoleCommand *dcc = new DuktapeConsoleCommand(ctx, obj_idx, cmd, filename);
    m_cmdmap[cmd] = dcc;
    return true;
    }
  else
    {
    ESP_LOGE(TAG,"Duktape: Could not register command %s/%s for script %s",
      parent, name, filename);
    return false;
    }
  }

void OvmsDuktape::DukTapeInit()
  {
  #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
    // Allocate dedicated UMM heap space:
    int memsize = MyConfig.GetParamValueInt("module", "duktape.heapsize",
      CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM_DEFAULTSIZE) * 1024;
    if (memsize <= 0)
      memsize = 512 * 1024;
    else if (memsize > UMM_MAX_BLOCKS * UMM_BLOCK_BODY_SIZE)
      memsize = UMM_MAX_BLOCKS * UMM_BLOCK_BODY_SIZE;
    ESP_LOGI(TAG, "Duktape: Creating heap (size: %u bytes)", memsize);
    umm_memory = ExternalRamMalloc(memsize);
    if (!umm_memory)
      {
      ESP_LOGE(TAG, "Duktape: unable to allocate %u bytes for the heap", memsize);
      return;
      }
    umm_init_heap(umm_memory, memsize);
  #else
    ESP_LOGI(TAG, "Duktape: Creating heap");
  #endif

  m_dukctx = duk_create_heap(DukOvmsAlloc,
    DukOvmsRealloc,
    DukOvmsFree,
    this,
    DukOvmsFatalHandler);

  ESP_LOGI(TAG,"Duktape: Initialising module system");
  duk_push_object(m_dukctx);
  duk_push_c_function(m_dukctx, DukOvmsResolveModule, DUK_VARARGS);
  duk_put_prop_string(m_dukctx, -2, "resolve");
  duk_push_c_function(m_dukctx, DukOvmsLoadModule, DUK_VARARGS);
  duk_put_prop_string(m_dukctx, -2, "load");
  duk_module_node_init(m_dukctx);

  if (m_fnmap.size() > 0)
    {
    // We have some functions to register...
    DuktapeFunctionMap::iterator itm=m_fnmap.begin();
    while (itm!=m_fnmap.end())
      {
      const char* name = itm->first;
      duktape_registerfunction_t* fn = itm->second;
      ESP_LOGD(TAG,"Duktape: Pre-Registered function %s",name);
      duk_push_c_function(m_dukctx, fn->func, fn->nargs);
      duk_put_global_string(m_dukctx, name);
      ++itm;
      }
    }

  if (m_obmap.size() > 0)
    {
    // We have some objects to register...
    DuktapeObjectMap::iterator itm=m_obmap.begin();
    while (itm!=m_obmap.end())
      {
      const char* name = itm->first;
      DuktapeObjectRegistration* ob = itm->second;
      ESP_LOGD(TAG,"Duktape: Pre-Registered object %s",name);
      ob->RegisterWithDuktape(m_dukctx);
      ++itm;
      }
    }

  // Load registered modules
  if (m_modmap.size() > 0)
    {
    // We have some modules to register...
    DuktapeModuleMap::iterator itm=m_modmap.begin();
    while (itm!=m_modmap.end())
      {
      const char* name = itm->first;
      ESP_LOGD(TAG,"Duktape: Pre-Registered module %s",name);
      std::string loadfn("(function(){");
      loadfn.append(name);
      loadfn.append("=require(\"int/");
      loadfn.append(name);
      loadfn.append("\");})();");
      duk_push_string(m_dukctx, loadfn.c_str());
      if (duk_peval(m_dukctx) != 0)
        {
        ESP_LOGE(TAG,"Duktape: %s",duk_safe_to_string(m_dukctx, -1));
        }
      duk_pop(m_dukctx);
      ++itm;
      }
    }

  #ifdef CONFIG_OVMS_COMP_PLUGINS
  // Plugins
  MyPluginStore.LoadEnabledModules(EL_MODULE);
  #endif // #ifdef CONFIG_OVMS_COMP_PLUGINS

  // ovmsmain
  FILE* sf = fopen("/store/scripts/ovmsmain.js", "r");
  if (sf != NULL)
    {
    fseek(sf,0,SEEK_END);
    long slen = ftell(sf);
    fseek(sf,0,SEEK_SET);
    char *script = new char[slen+1];
    memset(script,0,slen+1);
    fread(script,1,slen,sf);
    duk_push_string(m_dukctx, script);
    delete [] script;
    fclose(sf);
    ESP_LOGI(TAG,"Duktape: Executing ovmsmain.js");
    NotifyDuktapeModuleLoad("ovmsmain.js");
    duk_module_node_peval_main(m_dukctx, "ovmsmain.js");
    NotifyDuktapeModuleUnload("ovmsmain.js");
    }
  }

void OvmsDuktape::DukTapeTask()
  {
  duktape_queue_t msg;

  ESP_LOGI(TAG,"Duktape: Scripting task is running");
  esp_task_wdt_add(NULL); // WATCHDOG is active for this task

  while(1)
    {
    if (xQueueReceive(m_duktaskqueue, &msg, pdMS_TO_TICKS(5000))==pdTRUE)
      {
      esp_task_wdt_reset(); // Reset WATCHDOG timer for this task
      duktapewriter = msg.writer;
      switch(msg.type)
        {
        case DUKTAPE_reload:
          {
          // Reload DUKTAPE engine
          NotifyDuktapeModuleUnloadAll();
          if (m_dukctx != NULL)
            {
            ESP_LOGI(TAG,"Duktape: Clearing existing context");
            duk_destroy_heap(m_dukctx);
            #ifdef CONFIG_OVMS_SC_JAVASCRIPT_DUKTAPE_HEAP_UMM
              if (umm_memory != NULL)
                {
                free(umm_memory);
                umm_memory = NULL;
                }
            #endif
            m_dukctx = NULL;
            }
          DukTapeInit();
          }
          break;
        case DUKTAPE_compact:
          {
          // Compact DUKTAPE memory
          if (m_dukctx != NULL)
            {
            ESP_LOGV(TAG,"Duktape: Compacting DukTape memory");
            uint32_t ts = esp_log_timestamp();
            duk_gc(m_dukctx, 0);
            duk_gc(m_dukctx, 0);
            ESP_LOGD(TAG, "Duktape: Compacting DukTape memory done in %u ms", esp_log_timestamp()-ts);
            }
          }
          break;
        case DUKTAPE_event:
          {
          // Event
          if (m_dukctx != NULL)
            {
            // Deliver the event to DUKTAPE
            uint32_t ts = esp_log_timestamp();
            duk_require_stack(m_dukctx, 5);
            duk_get_global_string(m_dukctx, "PubSub");
            duk_get_prop_string(m_dukctx, -1, "publish");
            duk_dup(m_dukctx, -2);  /* this binding = process */
            duk_push_string(m_dukctx, msg.body.dt_event.name);
            duk_push_string(m_dukctx, "");
            if (duk_pcall_method(m_dukctx, 2) != 0)
              {
              DukOvmsErrorHandler(m_dukctx, -1);
              }
            duk_pop_2(m_dukctx);
            ts = esp_log_timestamp() - ts;
            if (ts > 1000)
              ESP_LOGW(TAG, "Duktape: event handling for '%s' took %u ms", msg.body.dt_event.name, ts);
            }
          }
          free((void*)msg.body.dt_event.name);
          break;
        case DUKTAPE_autoinit:
          {
          // Auto init
          DukTapeInit();
          }
          break;
        case DUKTAPE_evalnoresult:
          if (m_dukctx != NULL)
            {
            // Execute script text (without result)
            const char* filename = msg.body.dt_evalnoresult.filename;
            if (!filename) filename = "eval";
            duk_push_string(m_dukctx, msg.body.dt_evalnoresult.text);
            duk_push_string(m_dukctx, filename);
            if (duk_pcompile(m_dukctx, DUK_COMPILE_EVAL) != 0 || duk_pcall(m_dukctx, 0) != 0)
              {
              DukOvmsErrorHandler(m_dukctx, -1, msg.writer, filename);
              }
            duk_pop(m_dukctx);
            }
          else
            {
            if (msg.writer)
              msg.writer->puts("ERROR: Duktape not started");
            else
              ESP_LOGE(TAG, "Duktape not started");
            }
          break;
        case DUKTAPE_evalfloatresult:
          if (m_dukctx != NULL)
            {
            // Execute script text (float result)
            duk_push_string(m_dukctx, msg.body.dt_evalfloatresult.text);
            if (duk_peval(m_dukctx) != 0)
              {
              DukOvmsErrorHandler(m_dukctx, -1, msg.writer);
              *msg.body.dt_evalfloatresult.result = 0;
              }
            else
              {
              *msg.body.dt_evalfloatresult.result = (float)duk_get_number(m_dukctx,-1);
              }
            duk_pop(m_dukctx);
            }
          else
            {
            if (msg.writer)
              msg.writer->puts("ERROR: Duktape not started");
            else
              ESP_LOGE(TAG, "Duktape not started");
            *msg.body.dt_evalfloatresult.result = 0;
            }
          break;
        case DUKTAPE_evalintresult:
          if (m_dukctx != NULL)
            {
            // Execute script text (int result)
            duk_push_string(m_dukctx, msg.body.dt_evalintresult.text);
            if (duk_peval(m_dukctx) != 0)
              {
              DukOvmsErrorHandler(m_dukctx, -1, msg.writer);
              *msg.body.dt_evalintresult.result = 0;
              }
            else
              {
              *msg.body.dt_evalintresult.result = (int)duk_get_int(m_dukctx,-1);
              }
            duk_pop(m_dukctx);
            }
          else
            {
            if (msg.writer)
              msg.writer->puts("ERROR: Duktape not started");
            else
              ESP_LOGE(TAG, "Duktape not started");
            *msg.body.dt_evalintresult.result = 0;
            }
          break;
        case DUKTAPE_callback:
          {
          // DuktapeObject callback (without result)
          DuktapeObject* dto = msg.body.dt_callback.instance;
          dto->DuktapeCallback(m_dukctx, msg);
          }
          break;
        default:
          ESP_LOGE(TAG,"Duktape: Unrecognised msg type 0x%04x",msg.type);
          break;
        }
      duktapewriter = NULL;
      if (msg.waitcompletion)
        {
        // Signal the completion...
        xSemaphoreGive(msg.waitcompletion);
        }
      }
    esp_task_wdt_reset(); // Reset WATCHDOG timer for this task
    }
  }
