#include "qjs_actor.h"
#include "jsffi.h"
#include "qjs_macros.h"
#include "qjs_wota.h"
#include "cell.h"

#include <SDL3/SDL.h>
#include <stdlib.h>
#include <string.h>

// External function declarations
JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv);
JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv);

cell_rt *js2actor(JSContext *js, JSValue v)
{
  if (!JS_IsObject(v))
    return NULL;

  cell_rt *crt = JS_GetContextOpaque(js);
  JSValue actor_data = JS_GetProperty(js, v, crt->actor_sym);
  
  if (JS_IsNull(actor_data)) {
    JS_FreeValue(js, actor_data);
    return NULL;
  }
  
  JSValue id_val = JS_GetPropertyStr(js, actor_data, "id");
  JS_FreeValue(js, actor_data);
  
  if (JS_IsNull(id_val)) {
    JS_FreeValue(js, id_val);
    return NULL;
  }
  
  const char *id = JS_ToCString(js, id_val);
  JS_FreeValue(js, id_val);
  
  if (!id)
    return NULL;
    
  cell_rt *actor = get_actor((char*)id);
  JS_FreeCString(js, id);
  
  return actor;
}

JSValue actor2js(JSContext *js, cell_rt *actor)
{
  if (!actor)
    return JS_NULL;
    
  JSValue actor_obj = JS_NewObject(js);
  if (JS_IsException(actor_obj))
    return actor_obj;
    
  JSValue actor_data = JS_NewObject(js);
  if (JS_IsException(actor_data)) {
    JS_FreeValue(js, actor_obj);
    return actor_data;
  }
  
  JS_SetPropertyStr(js, actor_data, "id", JS_NewString(js, actor->id));
  
  /* TODO: If the actor has network info, we could add address and port here */
  /* JS_SetPropertyStr(js, actor_data, "address", JS_NewString(js, actor->address)); */
  /* JS_SetPropertyStr(js, actor_data, "port", JS_NewInt32(js, actor->port)); */
  
  cell_rt *crt = JS_GetContextOpaque(js);
  
  JS_SetProperty(js, actor_obj, crt->actor_sym, actor_data);
  
  return actor_obj;
}

JSC_CCALL(os_createactor,
  cell_rt *rt = JS_GetContextOpaque(js);
  if (rt->disrupt)
    return JS_ThrowInternalError(js, "Can't start a new actor while disrupting.");

  void *startup = value2wota(js, argv[0], JS_NULL, NULL);
  create_actor(startup);
)

JSC_CCALL(os_mailbox_push,
  if (argc < 2) return JS_ThrowInternalError(js, "Need an actor and a message");
  if (!js_is_blob(js, argv[1])) return JS_ThrowInternalError(js, "Can only send blobs.");

  const char *id = JS_ToCString(js, argv[0]);
  int exist = actor_exists(id);

  if (!exist) {
    JS_FreeCString(js,id);
    return JS_ThrowInternalError(js, "No mailbox found for given ID");
  }
  
  cell_rt *target = get_actor(id);
  JS_FreeCString(js,id);  
    
/*  JSValue sys = JS_GetPropertyStr(js, argv[1], "__SYSTEM__");
  if (!JS_IsNull(sys)) {
    const char *k = JS_ToCString(js,sys);
    int stop = 0;
    if (strcmp(k, "stop") == 0) {
      stop = 1;
      actor_disrupt(target);
    }
    JS_FreeValue(js,sys);
    JS_FreeCString(js,k);
    
    if (stop) return JS_NULL;
  }
*/    
  size_t size;
  void *data = js_get_blob_data(js, &size, argv[1]);
  
  // Create a new blob and copy the data
  blob *msg_blob = blob_new(size * 8); // Convert bytes to bits
  if (!msg_blob) {
    return JS_ThrowInternalError(js, "Could not allocate blob");
  }
  
  // Copy data to blob
  memcpy(msg_blob->data, data, size);
  msg_blob->length = size * 8;
  blob_make_stone(msg_blob); // Make it immutable
  
  const char *err = send_message(id, msg_blob);
  if (err) {
    blob_destroy(msg_blob);
    return JS_ThrowInternalError(js, "Could not send message: %s", err);
  }
)

JSC_CCALL(os_register_actor,
  cell_rt *rt = JS_GetContextOpaque(js);
  const char *id = JS_ToCString(js, argv[0]);
  double ar;
  JS_ToFloat64(js, &ar, argv[3]);
  const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar);
  if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err);
  rt->message_handle = JS_DupValue(js, argv[1]);
  rt->context = js;
  JS_FreeCString(js, id);
)

JSC_CCALL(os_mailbox_exist,
  const char *id = JS_ToCString(js, argv[0]);
  int exist = actor_exists(id);
  JS_FreeCString(js, id);

  return JS_NewBool(js, exist);
)

JSC_CCALL(os_unneeded,
  cell_rt *actor = JS_GetContextOpaque(js);
  double secs;
  JS_ToFloat64(js, &secs, argv[1]);
  actor_unneeded(actor, argv[0], secs);
)

JSC_CCALL(actor_disrupt,
  cell_rt *crt = JS_GetContextOpaque(js);
  actor_disrupt(crt);
)

JSC_SCALL(actor_setname,
  cell_rt *rt = JS_GetContextOpaque(js);
  rt->name = strdup(str);
)

JSC_CCALL(actor_on_exception,
  cell_rt *rt = JS_GetContextOpaque(js);
  JS_FreeValue(js, rt->on_exception);
  rt->on_exception = JS_DupValue(js,argv[0]);
)

JSC_CCALL(actor_clock,
  if (!JS_IsFunction(js, argv[0]))
    return JS_ThrowReferenceError(js, "Argument must be a function.");
    
  cell_rt *actor = JS_GetContextOpaque(js);
  actor_clock(actor, argv[0]);
)

static const JSCFunctionListEntry js_actor_funcs[] = {
  MIST_FUNC_DEF(os, createactor, 1),
  MIST_FUNC_DEF(os, mailbox_push, 2),
  MIST_FUNC_DEF(os, mailbox_exist, 1),
  MIST_FUNC_DEF(actor, delay, 2),
  MIST_FUNC_DEF(actor, removetimer, 1),
  MIST_FUNC_DEF(os, register_actor, 4),
  MIST_FUNC_DEF(os, unneeded, 2),
  MIST_FUNC_DEF(actor, disrupt, 0),
  MIST_FUNC_DEF(actor, setname, 1),
  MIST_FUNC_DEF(actor, on_exception, 1),
  MIST_FUNC_DEF(actor, clock, 1),
};

JSValue js_actor_use(JSContext *js) {
  JSValue mod = JS_NewObject(js);
  JS_SetPropertyFunctionList(js,mod,js_actor_funcs,countof(js_actor_funcs));
  return mod;
}