#include "qjs_rtree.h"
#include "qjs_macros.h"
#include "jsffi.h"
#include "rtree.h"
#include "cell.h"
#include <stdlib.h>
#include <stdbool.h>

// External declarations
typedef struct rtree rtree;

void rtree_free(JSRuntime *rt, rtree *tree)
{
  rtree_destroy(tree);
}

QJSCLASS(rtree,)

JSC_CCALL(rtree_add,
  rtree *tree = js2rtree(js,self);
  JSValue v = argv[0];
  rect r;
  JS_GETATOM(js,r,v,rect,rect)
  NUMTYPE min[3];
  NUMTYPE max[3];
  min[0] = r.x;
  min[1] = r.y;
  min[2] = 0;
  max[0] = r.x+r.w;
  max[1] = r.y+r.h;
  max[2] = 0;
  JSValue *ins = malloc(sizeof(*ins));
  *ins = JS_DupValue(js,v);
  if (!rtree_insert(tree, min, max, ins)) {
    JS_FreeValue(js,*ins);
    return JS_ThrowOutOfMemory(js);
  }
)

int rtree_cmp(const JSValue *a, const JSValue *b, JSContext *js)
{
  int same = JS_SameValue(js, *a, *b);
  if (same)
    JS_FreeValue(js,*a);

  return !same;
}

JSC_CCALL(rtree_delete,
  rtree *tree = js2rtree(js,self);
  JSValue v = argv[0];
  rect r;
  JS_GETATOM(js,r,v,rect,rect)
  NUMTYPE min[3];
  NUMTYPE max[3];
  min[0] = r.x;
  min[1] = r.y;
  min[2] = 0;
  max[0] = r.x+r.w;
  max[1] = r.y+r.h;
  max[2] = 0;
  
  if (!rtree_delete_with_comparator(tree, min, max, &v, rtree_cmp, js))
    return JS_ThrowOutOfMemory(js);
)

struct rtree_iter_data {
  JSContext *js;
  JSValue arr;
  int n;
};

bool rtree_iter(const NUMTYPE *min, const NUMTYPE *max, const JSValue *data, struct rtree_iter_data *ctx)
{
  JS_SetPropertyUint32(ctx->js,ctx->arr,ctx->n, JS_DupValue(ctx->js,*data));
  ctx->n++;
  return 1;
}

JSC_CCALL(rtree_query,
  rtree *tree = js2rtree(js,self);
  rect r = js2rect(js,argv[0]);
  NUMTYPE min[3];
  NUMTYPE max[3];
  min[0] = r.x;
  min[1] = r.y;
  min[2] = 0;
  max[0] = r.x+r.w;
  max[1] = r.y+r.h;
  max[2] = 0;

  struct rtree_iter_data data = {0};
  data.js = js;
  data.arr = JS_NewArray(js);
  data.n = 0;
  rtree_search(tree, min, max, rtree_iter, &data);
  ret = data.arr;
)

struct rtree_each
{
  JSValue fn;
  JSContext *js;
};

int rtree_foreach(const NUMTYPE *min, const NUMTYPE *max, const JSValue *value, struct rtree_each *each)
{
  JSValue ret = JS_Call(each->js, each->fn, JS_NULL, 0, NULL);
  uncaught_exception(each->js, ret);
  return 1;
}

JSC_CCALL(rtree_forEach,
  rtree *tree = js2rtree(js,self);
  struct rtree_each each;
  each.fn = JS_DupValue(js,argv[0]);
  each.js = js;

  rtree_scan(tree, rtree_foreach, &each);
  JS_FreeValue(js,each.fn);
)

typedef struct {
  JSContext *js;
  JSValue v;
  int has;
} rtree_has;

int rtree_hasfn(const NUMTYPE *min, const NUMTYPE *max, const JSValue *value, rtree_has *has)
{
  if (JS_SameValue(has->js, has->v, *value)) {
    has->has = 1;
    return 0;
  }
  return 1;
}

JSC_CCALL(rtree_has,
  rtree *tree = js2rtree(js,self);
  rtree_has has;
  has.js = js;
  has.v = JS_DupValue(js,argv[0]);
  has.has = 0;
  rtree_scan(tree, rtree_hasfn, &has);
  JS_FreeValue(js,argv[0]);
  return JS_NewBool(js,has.has);
)

JSValue js_rtree_get_size(JSContext *js, JSValue self, int magic)
{
  rtree *tree = js2rtree(js,self);
  return number2js(js,rtree_count(tree));
}

int rtree_valuefn(const NUMTYPE *min, const NUMTYPE *max, const JSValue *value, struct rtree_iter_data *data)
{
  JS_SetPropertyUint32(data->js, data->arr, data->n, JS_DupValue(data->js, *value));
  data->n++;
  return 1;
}

JSC_CCALL(rtree_values,
  rtree *tree = js2rtree(js,self);
  struct rtree_iter_data data = {0};
  data.js = js;
  data.arr = JS_NewArray(js);
  data.n = 0;
  rtree_scan(tree, rtree_valuefn, &data);

  ret = data.arr;
)

// Constructor function for rtree
static JSValue js_rtree_constructor(JSContext *js, JSValueConst new_target, int argc, JSValueConst *argv) {
  struct rtree *tree = rtree_new();
  if (!tree) return JS_ThrowOutOfMemory(js);
  return rtree2js(js,tree);
}

static const JSCFunctionListEntry js_rtree_funcs[] = {
  MIST_FUNC_DEF(rtree, add, 1),
  MIST_FUNC_DEF(rtree, delete, 1),
  MIST_FUNC_DEF(rtree, query, 1),
  JS_CGETSET_DEF("size", js_rtree_get_size,NULL),
  MIST_FUNC_DEF(rtree, forEach, 1),
  MIST_FUNC_DEF(rtree, has, 1),
  MIST_FUNC_DEF(rtree,values,0),
};

JSValue js_rtree_use(JSContext *js) {
  // Register the rtree class
  QJSCLASSPREP_FUNCS(rtree);
  
  // Create the constructor function
  JSValue ctor = JS_NewCFunction2(js, js_rtree_constructor, "rtree", 0, JS_CFUNC_constructor, 0);
  
  // Set the prototype on the constructor
  JSValue proto = JS_GetClassProto(js, js_rtree_id);
  JS_SetConstructor(js, ctor, proto);
  JS_FreeValue(js, proto);
  
  return ctor;
}