#include "cell.h"
#include "cell_internal.h"
#include "quickjs-internal.h"

#include <stdlib.h>
#include <string.h>

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

  void *startup = value2wota(js, argv[0], JS_NULL, NULL);
  if (!startup)
    return JS_RaiseDisrupt(js, "Failed to encode startup data");
  create_actor(startup);
)

JSC_CCALL(os_mailbox_push,
  if (argc < 2) return JS_RaiseDisrupt(js, "Need an actor and a message");
  if (!js_is_blob(js, argv[1])) return JS_RaiseDisrupt(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_RaiseDisrupt(js, "No mailbox found for given ID");
  }

  size_t size;
  void *data = js_get_blob_data(js, &size, argv[1]);
  if (data == (void*)-1) {
    JS_FreeCString(js, id);
    return JS_EXCEPTION;
  }
  if (size == 0) {
    JS_FreeCString(js, id);
    return JS_RaiseDisrupt(js, "No data present in blob");
  }

  blob *msg_blob = blob_new(size * 8);
  if (!msg_blob) {
    JS_FreeCString(js, id);
    return JS_RaiseDisrupt(js, "Could not allocate blob");
  }

  blob_write_bytes(msg_blob, data, size);
  blob_make_stone(msg_blob);

  const char *err = send_message(id, msg_blob);
  JS_FreeCString(js, id);
  if (err) {
    blob_destroy(msg_blob);
    return JS_RaiseDisrupt(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_RaiseDisrupt(js, "Could not register actor: %s", err);
  rt->message_handle_ref.val = 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);
  rt->on_exception_ref.val = argv[0];
)

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

JSC_CCALL(actor_delay,
   if (!JS_IsFunction(argv[0]))
    return JS_RaiseDisrupt(js, "Argument must be a function.");

  cell_rt *actor = JS_GetContextOpaque(js);
  double seconds;
  JS_ToFloat64(js, &seconds, argv[1]);
  if (seconds <= 0) {
    actor_clock(actor, argv[0]);
    return JS_NULL;
  }

  uint32_t id = actor_delay(actor, argv[0], seconds);
  return JS_NewUint32(js, id);
)

JSC_CCALL(actor_removetimer,
  cell_rt *actor = JS_GetContextOpaque(js);
  uint32_t timer_id;
  JS_ToUint32(js, &timer_id, argv[0]);
  JSValue removed = actor_remove_timer(actor, timer_id);
  JS_FreeValue(js, removed);
)

/* Log callback bridge: called from JS_Log, calls ƿit log(channel, [msg, stack])
   Captures the register VM stack trace so the log system can show the real error site. */
static void js_log_callback(JSContext *ctx, const char *channel, const char *msg) {
  if (JS_IsNull(ctx->log_callback_js)) {
    fprintf(stderr, "%s\n", msg);
    return;
  }
  JS_FRAME(ctx);
  JS_ROOT(stack, JS_GetStack(ctx));
  JS_ROOT(args_array, JS_NewArray(ctx));
  JS_SetPropertyNumber(ctx, args_array.val, 0, JS_NewString(ctx, msg));
  JS_SetPropertyNumber(ctx, args_array.val, 1, stack.val);
  JSValue argv[2];
  argv[0] = JS_NewString(ctx, channel);
  argv[1] = args_array.val;
  JS_Call(ctx, ctx->log_callback_js, JS_NULL, 2, argv);
  JS_RestoreFrame(ctx, _js_gc_frame, _js_local_frame);
}

JSC_CCALL(actor_set_log,
  if (argc < 1 || !JS_IsFunction(argv[0]))
    return JS_RaiseDisrupt(js, "set_log requires a function");
  js->log_callback_js = argv[0];
  js->log_callback = js_log_callback;
)

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),
  MIST_FUNC_DEF(actor, set_log, 1),
};

JSValue js_core_actor_use(JSContext *js) {
  JS_FRAME(js);
  JS_ROOT(mod, JS_NewObject(js));
  JS_SetPropertyFunctionList(js, mod.val, js_actor_funcs, countof(js_actor_funcs));
  JS_RETURN(mod.val);
}
