/*
;    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.
*/

#ifndef __OVMS_DUKTAPE_H__
#define __OVMS_DUKTAPE_H__

#include "ovms_command.h"
#include "ovms_utils.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"

#include "duktape.h"
#include <list>
#include <utility>

////////////////////////////////////////////////////////////////////////////////
// DukContext: C++ wrapper for duk_context

class DukContext
  {
  public:
    DukContext(duk_context *ctx) { m_ctx = ctx; }
    ~DukContext() {}

    void Push(bool val)                   { duk_push_boolean(m_ctx, val); }
    void Push(int val)                    { duk_push_number(m_ctx, val); }
    void Push(short val)                  { duk_push_number(m_ctx, val); }
    void Push(long val)                   { duk_push_number(m_ctx, val); }
    void Push(unsigned int val)           { duk_push_uint(m_ctx, val); }
    void Push(unsigned short val)         { duk_push_uint(m_ctx, val); }
    void Push(unsigned long val)          { duk_push_uint(m_ctx, val); }
    void Push(float val)                  { duk_push_number(m_ctx, float2double(val)); }
    void Push(double val)                 { duk_push_number(m_ctx, val); }
    void Push(const char* val)            { duk_push_string(m_ctx, val); }
    void Push(const std::string &val)     { duk_push_lstring(m_ctx, val.data(), val.size()); }
    void Push(const extram::string &val)  { duk_push_lstring(m_ctx, val.data(), val.size()); }

    // Push string as Uint8Array:
    void PushBinary(const std::string &val)
      {
      void* p = duk_push_fixed_buffer(m_ctx, val.size());
      if (p) memcpy(p, val.data(), val.size());
      }
    void PushBinary(const extram::string &val)
      {
      void* p = duk_push_fixed_buffer(m_ctx, val.size());
      if (p) memcpy(p, val.data(), val.size());
      }

    duk_idx_t PushArray()                 { return duk_push_array(m_ctx); }
    duk_idx_t PushObject()                { return duk_push_object(m_ctx); }
    duk_bool_t PutProp(duk_idx_t obj_idx, duk_uarridx_t arr_idx)
      { return duk_put_prop_index(m_ctx, obj_idx, arr_idx); }
    duk_bool_t PutProp(duk_idx_t obj_idx, const char* prop_name)
      { return duk_put_prop_string(m_ctx, obj_idx, prop_name); }

  public:
    duk_context *m_ctx;
  };

////////////////////////////////////////////////////////////////////////////////
// Duktape Task Commands

typedef enum
  {
  DUKTAPE_none = 0,             // Do nothing
  DUKTAPE_reload,               // Reload DukTape engine
  DUKTAPE_compact,              // Compact DukTape memory
  DUKTAPE_event,                // Event
  DUKTAPE_autoinit,             // Auto init
  DUKTAPE_evalnoresult,         // Execute script text (without result)
  DUKTAPE_evalfloatresult,      // Execute script text (float result)
  DUKTAPE_evalintresult,        // Execute script text (int result)
  DUKTAPE_callback,             // DuktapeObject callback
  } duktape_msg_t;

////////////////////////////////////////////////////////////////////////////////
// DuktapeObjectRegistration

typedef struct
  {
  duk_c_function func;
  duk_idx_t nargs;
  } duktape_registerfunction_t;

typedef std::map<const char*, duktape_registerfunction_t*, CmpStrOp> DuktapeFunctionMap;

typedef struct
  {
  const char* start;
  size_t length;
  } duktape_registermodule_t;

typedef std::map<const char*, duktape_registermodule_t*, CmpStrOp> DuktapeModuleMap;

class DuktapeObjectRegistration
  {
  public:
    DuktapeObjectRegistration(const char* name);
    ~DuktapeObjectRegistration();

  public:
    const char* GetName();
    void RegisterDuktapeFunction(duk_c_function func, duk_idx_t nargs, const char* name);

  public:
    void RegisterWithDuktape(duk_context* ctx);

  protected:
    const char* m_name;
    DuktapeFunctionMap m_fnmap;
  };

typedef std::map<const char*, DuktapeObjectRegistration*, CmpStrOp> DuktapeObjectMap;
class DuktapeObject;
class OvmsScripts;

typedef struct
  {
  union
    {
    struct
      {
      const char* name;
      void* data;
      } dt_event;
    struct
      {
      const char* text;
      const char* filename;
      } dt_evalnoresult;
    struct
      {
      const char* text;
      float* result;
      } dt_evalfloatresult;
    struct
      {
      const char* text;
      int* result;
      } dt_evalintresult;
    struct
      {
      DuktapeObject* instance;
      const char* method;
      void* data;
      } dt_callback;
    } body;
  duktape_msg_t type;
  QueueHandle_t waitcompletion;
  OvmsWriter* writer;
  } duktape_queue_t;

////////////////////////////////////////////////////////////////////////////////
// DuktapeObject: coupled C++ / JS object
//
//  Intended for API methods to attach internal API state to a JS object and provide
//    a standard callback invocation interface for JS objects in local scopes.
//
//  - Override CallMethod() to implement specific method calls
//  - Override Finalize() for specific destruction in JS context (garbage collection)
//  - call Register() to prevent normal garbage collection (but not heap destruction)
//  - call Ref() to protect against deletion (reference count)
//  - call Lock() to protect concurrent access (recursive mutex)
//
//  - GetInstance() retrieves the DuktapeObject associated with a JS object if any
//  - Push() pushes the JS object onto the Duktape stack
//
//  Note: the DuktapeObject may persist after the JS object has been finalized, e.g.
//    if some callbacks are pending after the Duktape heap has been destroyed.
//    Use IsCoupled() to check if the JS object is still available.
//
//  Ref/Unref:
//    Normal life cycle is from construction to finalization. Pending callbacks extend
//    the life until the last callback has been processed. A subclass may extend the life
//    by calling Ref(), which increases the reference count. Unref() deletes the instance
//    if no references are left.

class DuktapeObject
  {
  friend OvmsScripts;
  public:
    DuktapeObject();
    DuktapeObject(duk_context *ctx, int obj_idx);
    virtual ~DuktapeObject();

  public:
    bool Lock(TickType_t timeout = portMAX_DELAY) { return m_mutex.Lock(timeout); }
    void Unlock() { m_mutex.Unlock(); }

  public:
    void Ref();
    bool Unref();

  public:
    bool Couple(duk_context *ctx, int obj_idx);
    void Decouple(duk_context *ctx);
    bool IsCoupled() { return m_object != NULL; }
    static DuktapeObject* GetInstance(duk_context *ctx, int obj_idx);

  protected:
    static duk_ret_t DuktapeFinalizer(duk_context *ctx);
    virtual void Finalize(duk_context *ctx, bool heapDestruct);

  public:
    void Register(duk_context *ctx);
    void Register(duk_context *ctx, int obj_idx);
    void Deregister(duk_context *ctx);
    bool IsRegistered() { return m_registered; }

  public:
    void RequestCallback(const char* method, void* data=NULL);
    duk_ret_t DuktapeCallback(duk_context *ctx, duktape_queue_t &msg);
    virtual duk_ret_t CallMethod(duk_context *ctx, const char* method, void* data=NULL);

  public:
    duk_idx_t Push(duk_context *ctx);

  protected:
    OvmsRecMutex m_mutex;               // concurrency lock
    uint32_t m_refcnt = 0;              // references to this object (1→0 = deletion)
    void* m_object = NULL;              // heapptr to JS object
    bool m_registered = false;          // registration state (true = GC prevented)
    void* m_registry = NULL;            // optional heapptr to specific registry JS object
  };

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

class DuktapeConsoleCommand : public DuktapeObject
  {
  public:
    DuktapeConsoleCommand(duk_context *ctx, int obj_idx, OvmsCommand* cmd, const char* module);
    ~DuktapeConsoleCommand();

  public:
    OvmsCommand* m_cmd;
    std::string m_module;
  };

////////////////////////////////////////////////////////////////////////////////
// OvmsDuktape
//
// Container for object registration and the main Duktape task

class OvmsDuktape
  {
  public:
    OvmsDuktape();
    ~OvmsDuktape();

  public:
    void RegisterDuktapeFunction(duk_c_function func, duk_idx_t nargs, const char* name);
    void RegisterDuktapeModule(const char* start, size_t length, const char* name);
    duktape_registermodule_t* FindDuktapeModule(const char* name);
    void RegisterDuktapeObject(DuktapeObjectRegistration* ob);
    void AutoInitDuktape();

  protected:
    bool DuktapeDispatch(duktape_queue_t* msg, TickType_t queuewait=portMAX_DELAY);
    void DuktapeDispatchWait(duktape_queue_t* msg);

  public:
    void  DuktapeEvalNoResult(const char* text, OvmsWriter* writer=NULL, const char* filename=NULL);
    float DuktapeEvalFloatResult(const char* text, OvmsWriter* writer=NULL);
    int   DuktapeEvalIntResult(const char* text, OvmsWriter* writer=NULL);
    void  DuktapeReload();
    void  DuktapeCompact(bool wait=true);
    void  DuktapeRequestCallback(DuktapeObject* instance, const char* method, void* data);
    OvmsWriter* GetDuktapeWriter();
    void DukGetCallInfo(duk_context *ctx, std::string *filename, int *linenumber, std::string *function);

  public:
    void NotifyDuktapeModuleLoad(const char* filename);
    void NotifyDuktapeModuleUnload(const char* filename);
    void NotifyDuktapeModuleUnloadAll();
    bool RegisterDuktapeConsoleCommand(duk_context *ctx, int obj_idx,
                                       const char* filename,
                                       const char* parent,
                                       const char* name, const char* title,
                                       const char *usage = "", int min = 0, int max = 0);

  public:
    void DukTapeInit();
    void DukTapeTask();
    bool DukTapeAvailable() { return m_dukctx != NULL; }
    duk_context* DukTapeContext() { return m_dukctx; }
    void EventScript(std::string event, void* data);

  protected:
    duk_context* m_dukctx;
    TaskHandle_t m_duktaskid;
    QueueHandle_t m_duktaskqueue;
    DuktapeFunctionMap m_fnmap;
    DuktapeModuleMap m_modmap;
    DuktapeObjectMap m_obmap;

  public:
    typedef std::map<OvmsCommand*, DuktapeConsoleCommand*> DuktapeCommandMap;
    DuktapeCommandMap m_cmdmap;
  };

extern void DukOvmsErrorHandler(duk_context *ctx, duk_idx_t err_idx, OvmsWriter *writer=NULL, const char *filename=NULL);

extern OvmsDuktape MyDuktape;

#endif //#ifndef __OVMS_DUKTAPE_H__
