#include "quickjs.h"
#include <stdint.h>
#include <math.h>

#define LAY_FLOAT 1
#define LAY_IMPLEMENTATION
#include "layout.h"

static JSClassID js_layout_class_id;

#define GETLAY \
lay_context *lay = JS_GetOpaque2(js, self, js_layout_class_id); \
if (!lay) return JS_EXCEPTION;

#define GETITEM(VAR, ARG) \
lay_id VAR; \
if (JS_ToUint32(js, &VAR, ARG)) return JS_EXCEPTION; \

static JSValue js_layout_context_new(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  lay_context *lay = js_malloc(js, sizeof(*lay));
  lay_init_context(lay);
  JSValue obj = JS_NewObjectClass(js, js_layout_class_id);
  JS_SetOpaque(obj, lay);
  return obj;
}

static JSValue js_layout_item(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  GETLAY
  lay_id item_id = lay_item(lay);
  return JS_NewUint32(js,item_id);
}

static JSValue js_layout_set_size(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
{
  if (argc < 2) return JS_EXCEPTION;
  
  GETLAY
  GETITEM(id, argv[0])
  double width = 0, height = 0;
  
  // Check if it's an array (for backwards compatibility)
  if (JS_IsArray(js, argv[1])) {
    JSValue width_val = JS_GetPropertyUint32(js, argv[1], 0);
    JSValue height_val = JS_GetPropertyUint32(js, argv[1], 1);
    JS_ToFloat64(js, &width, width_val);
    JS_ToFloat64(js, &height, height_val);
    JS_FreeValue(js, width_val);
    JS_FreeValue(js, height_val);
  } else {
    // Handle object with x,y or width,height properties
    JSValue width_val = JS_GetPropertyStr(js, argv[1], "width");
    if (JS_IsNull(width_val)) {
      width_val = JS_GetPropertyStr(js, argv[1], "x");
    }
    JSValue height_val = JS_GetPropertyStr(js, argv[1], "height");
    if (JS_IsNull(height_val)) {
      height_val = JS_GetPropertyStr(js, argv[1], "y");
    }
    JS_ToFloat64(js, &width, width_val);
    JS_ToFloat64(js, &height, height_val);
    JS_FreeValue(js, width_val);
    JS_FreeValue(js, height_val);
  }
  
  if (isnan(width)) width = 0;
  if (isnan(height)) height = 0;
  lay_set_size_xy(lay, id, width, height);
  
  return JS_NULL;
}

static JSValue js_layout_set_behave(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
{
  GETLAY
  GETITEM(id, argv[0])
  uint32_t behave_flags;
  JS_ToUint32(js, &behave_flags, argv[1]);
  lay_set_behave(lay, id, behave_flags);
  return JS_NULL;
}

static JSValue js_layout_set_contain(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
{
  GETLAY
  GETITEM(id, argv[0])
  uint32_t contain_flags;
  JS_ToUint32(js, &contain_flags, argv[1]);
  lay_set_contain(lay, id, contain_flags);
  return JS_NULL;
}

#define PULLMARGIN(DIR,MARGIN) \
JSValue js_##DIR = JS_GetPropertyStr(js, argv[1], #DIR); \
if (!JS_IsNull(js_##DIR)) { \
  JS_ToFloat64(js, &margins[MARGIN], js_##DIR); \
  if (isnan(margins[MARGIN])) margins[MARGIN] = 0; \
  JS_FreeValue(js, js_##DIR); \
}\

static JSValue js_layout_set_margins(JSContext *js, JSValueConst self, int argc, JSValueConst *argv)
{
  GETLAY
  GETITEM(id, argv[0])
  double margins[4] = {0};
  PULLMARGIN(l,0)
  PULLMARGIN(r,2)
  PULLMARGIN(t,1)
  PULLMARGIN(b,3)
  lay_set_margins_ltrb(lay, id, margins[0], margins[1], margins[2], margins[3]);
  return JS_NULL;
}

static JSValue js_layout_insert(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  GETLAY
  GETITEM(parent, argv[0])
  GETITEM(child, argv[1])
  lay_insert(lay, parent, child);
  return JS_NULL;
}

static JSValue js_layout_append(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  GETLAY
  GETITEM(earlier, argv[0])
  GETITEM(later, argv[1])
  lay_append(lay, earlier, later);
  return JS_NULL;
}

static JSValue js_layout_push(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  GETLAY
  GETITEM(parent, argv[0])
  GETITEM(child, argv[1])
  lay_push(lay, parent, child);
  return JS_NULL;
}

static JSValue js_layout_run(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  GETLAY
  lay_run_context(lay);
  return JS_NULL;
}

static JSValue js_layout_get_rect(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  GETLAY
  GETITEM(id, argv[0])
  lay_vec4 rect = lay_get_rect(lay, id);
  JSValue ret = JS_NewObject(js);
  JS_SetPropertyStr(js, ret, "x", JS_NewFloat64(js, rect[0]));
  JS_SetPropertyStr(js, ret, "y", JS_NewFloat64(js, rect[1]));
  JS_SetPropertyStr(js, ret, "width", JS_NewFloat64(js, rect[2]));
  JS_SetPropertyStr(js, ret, "height", JS_NewFloat64(js, rect[3]));    

  return ret;
}

static JSValue js_layout_reset(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
  GETLAY
  lay_reset_context(lay);
  return JS_NULL;
}

static void js_layout_finalizer(JSRuntime *rt, JSValue val) {
  lay_context *ctx = JS_GetOpaque(val, js_layout_class_id);
  lay_destroy_context(ctx);
  js_free_rt(rt, ctx);
}

static JSClassDef js_layout_class = {
  "layout_context",
  .finalizer = js_layout_finalizer,
};

static const JSCFunctionListEntry js_layout_proto_funcs[] = {
  JS_CFUNC_DEF("item", 1, js_layout_item),
  JS_CFUNC_DEF("insert", 2, js_layout_insert),
  JS_CFUNC_DEF("append", 2, js_layout_append),
  JS_CFUNC_DEF("push", 2, js_layout_push),
  JS_CFUNC_DEF("run", 0, js_layout_run),
  JS_CFUNC_DEF("get_rect", 1, js_layout_get_rect),
  JS_CFUNC_DEF("reset", 0, js_layout_reset),
  JS_CFUNC_DEF("set_margins", 2, js_layout_set_margins),
  JS_CFUNC_DEF("set_size", 2, js_layout_set_size),
  JS_CFUNC_DEF("set_contain", 2, js_layout_set_contain),
  JS_CFUNC_DEF("set_behave", 2, js_layout_set_behave),
};

static const JSCFunctionListEntry js_layout_funcs[] = {
  JS_CFUNC_DEF("make_context", 0, js_layout_context_new)
};

JSValue js_layout_use(JSContext *js)
{
  JS_NewClassID(&js_layout_class_id);
  JS_NewClass(JS_GetRuntime(js), js_layout_class_id, &js_layout_class);

  JSValue proto = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, proto, js_layout_proto_funcs, sizeof(js_layout_proto_funcs) / sizeof(JSCFunctionListEntry));
  JS_SetClassProto(js, js_layout_class_id, proto);

  JSValue contain = JS_NewObject(js);
  JSValue behave = JS_NewObject(js);
  JS_SetPropertyStr(js, contain, "row", JS_NewUint32(js, LAY_ROW));
  JS_SetPropertyStr(js, contain, "column", JS_NewUint32(js, LAY_COLUMN));
  JS_SetPropertyStr(js, contain, "layout", JS_NewUint32(js, LAY_LAYOUT));
  JS_SetPropertyStr(js, contain, "flex", JS_NewUint32(js, LAY_FLEX));
  JS_SetPropertyStr(js, contain, "wrap", JS_NewUint32(js, LAY_WRAP));
  JS_SetPropertyStr(js, contain, "nowrap", JS_NewUint32(js, LAY_NOWRAP));
    
  JS_SetPropertyStr(js, contain, "start", JS_NewUint32(js, LAY_START));
  JS_SetPropertyStr(js, contain, "middle", JS_NewUint32(js, LAY_MIDDLE));    
  JS_SetPropertyStr(js, contain, "end", JS_NewUint32(js, LAY_END));
  JS_SetPropertyStr(js, contain, "justify", JS_NewUint32(js, LAY_JUSTIFY));
    
  JS_SetPropertyStr(js, behave, "left", JS_NewUint32(js, LAY_LEFT));
  JS_SetPropertyStr(js, behave, "top", JS_NewUint32(js, LAY_TOP));
  JS_SetPropertyStr(js, behave, "right", JS_NewUint32(js, LAY_RIGHT));
  JS_SetPropertyStr(js, behave, "bottom", JS_NewUint32(js, LAY_BOTTOM));
  JS_SetPropertyStr(js, behave, "hfill", JS_NewUint32(js, LAY_HFILL));
  JS_SetPropertyStr(js, behave, "vfill", JS_NewUint32(js, LAY_VFILL));
  JS_SetPropertyStr(js, behave, "hcenter", JS_NewUint32(js, LAY_HCENTER));
  JS_SetPropertyStr(js, behave, "vcenter", JS_NewUint32(js, LAY_VCENTER));
  JS_SetPropertyStr(js, behave, "center", JS_NewUint32(js, LAY_CENTER));    
  JS_SetPropertyStr(js, behave, "fill", JS_NewUint32(js, LAY_FILL));
  JS_SetPropertyStr(js, behave, "break", JS_NewUint32(js, LAY_BREAK));

  JSValue export = JS_NewObject(js);
  JS_SetPropertyFunctionList(js, export, js_layout_funcs, sizeof(js_layout_funcs)/sizeof(JSCFunctionListEntry));
  JS_SetPropertyStr(js, export, "behave", behave);
  JS_SetPropertyStr(js, export, "contain", contain);  

  return export;
}
