#include "transform.h"
#include <string.h>
#include <stdio.h>
#include "stb_ds.h"

static transform **dirties;

static transform model = {
  .pos = {0,0,0},
  .scale = {1,1,1},
  .rotation = {0,0,0,1},
  .cache = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
  .gcache = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
  .dirty = 0,
};

transform *make_transform()
{
  transform *t = malloc(sizeof(transform));
  *t = model;
  t->jsparent = JS_NULL;
  t->change_hook = JS_NULL;
  return t;
}

void transform_free(JSRuntime *rt, transform *t) {
//  JS_FreeValueRT(rt,t->self);
  JS_FreeValueRT(rt,t->change_hook);
  JS_FreeValueRT(rt,t->jsparent);
  for (int i = 0; i < arrlen(t->jschildren); i++)
    JS_FreeValueRT(rt,t->jschildren[i]);

  arrfree(t->jschildren);
  arrfree(t->children);
  free(t);

  for (int i = arrlen(dirties)-1; i >= 0; i--)
    if (dirties[i] == t) arrdelswap(dirties, i);
}

void transform_clean(JSContext *js, transform *t)
{
  if (!t->dirty) return;
  t->dirty = 0;
  t->cache = HMM_M4TRS(t->pos,t->rotation,t->scale);
  if (t->parent)
    t->gcache = HMM_MulM4(t->parent->gcache, t->cache);
  else
    t->gcache = t->cache;

  for (int i = 0; i < arrlen(t->children); i++) {
    t->children[i]->dirty = 1;
    transform_clean(js, t->children[i]);
  }

  if (!JS_IsNull(t->change_hook)) {
    JSValue ret = JS_Call(js, t->change_hook, JS_DupValue(js,t->self), 0, NULL);
    JS_FreeValue(js,ret);
  }
}

void clean_all(JSContext *js)
{
  for (int i = 0; i < arrlen(dirties); i++)
    transform_clean(js, dirties[i]);

  arrsetlen(dirties,0);
}

void transform_move(transform *t, HMM_Vec3 v)
{
  t->pos = HMM_AddV3(t->pos, v);
  transform_apply(t);
}

HMM_Vec3 transform_direction(transform *t, HMM_Vec3 dir)
{
  return HMM_QVRot(dir, t->rotation);
}

HMM_Vec3 trans_forward(const transform *const trans) { return HMM_QVRot(vFWD, trans->rotation); }
HMM_Vec3 trans_back(const transform *trans) { return HMM_QVRot(vBKWD, trans->rotation); }
HMM_Vec3 trans_up(const transform *trans) { return HMM_QVRot(vUP, trans->rotation); }
HMM_Vec3 trans_down(const transform *trans) { return HMM_QVRot(vDOWN, trans->rotation); }
HMM_Vec3 trans_right(const transform *trans) { return HMM_QVRot(vRIGHT, trans->rotation); }
HMM_Vec3 trans_left(const transform *trans) { return HMM_QVRot(vLEFT, trans->rotation); }

HMM_Vec2 mat_t_pos(HMM_Mat3 m, HMM_Vec2 pos) { return HMM_MulM3V3(m, (HMM_Vec3){pos.x, pos.y, 1}).xy; }

HMM_Vec2 mat_t_dir(HMM_Mat3 m, HMM_Vec2 dir)
{
  m.Columns[2] = (HMM_Vec3){0,0,1};
  return HMM_MulM3V3(m, (HMM_Vec3){dir.x, dir.y, 1}).XY;
}

HMM_Vec2 mat_up(HMM_Mat3 m) { return HMM_NormV2(m.Columns[1].XY); }
HMM_Vec2 mat_right(HMM_Mat3 m) { return HMM_NormV2(m.Columns[0].XY); }
float vec_angle(HMM_Vec2 a, HMM_Vec2 b) { return acos(HMM_DotV2(a,b)/(HMM_LenV2(a)*HMM_LenV2(b))); }
float vec_dirangle(HMM_Vec2 a, HMM_Vec2 b) { return atan2(b.x, b.y) - atan2(a.x, a.y); }

HMM_Vec3 mat3_t_pos(HMM_Mat4 m, HMM_Vec3 pos) { return HMM_MulM4V4(m, (HMM_Vec4){pos.X, pos.Y, pos.Z, 1}).XYZ; }

HMM_Vec3 mat3_t_dir(HMM_Mat4 m, HMM_Vec3 dir)
{
  m.Columns[3] = (HMM_Vec4){0,0,0,1};
  return mat3_t_pos(m, dir);
}

HMM_Mat4 transform2mat(transform *t)
{
  return t->cache;
}

HMM_Mat3 transform2mat3(transform *t)
{
  return HMM_M3TRS(t->pos.xy, t->rotation.x, t->scale.xy);
}

HMM_Mat4 transform2mat4_global(transform *t)
{
  return t->gcache;
}

rect transform2rect(transform *t)
{
  HMM_Mat4 m = t->gcache;
  rect r = {0};
  r.x = m.Columns[3].x;
  r.y = m.Columns[3].y;
  r.w = m.Columns[0].x;
  r.h = m.Columns[1].y;
  return r;
}

void transform_apply(transform *t)
{
  t->dirty = 1;
  arrput(dirties,t);
}

HMM_Quat angle2rotation(float angle)
{
  return HMM_QFromAxisAngle_RH(vBKWD, angle);
}

transform mat2transform(HMM_Mat4 m)
{
  transform t;
  t.pos = m.Columns[3].xyz;
  for (int i = 0; i < 2; i++)
    t.scale.Elements[i] = HMM_LenV3(m.Columns[i].xyz);
//  for (int i = 0; i < 2; i++)
//    m.Columns[i].xyz = HMM_MulV3(m.Columns[i].xyz, t.scale.Elements[i]);
  t.rotation = HMM_M4ToQ_RH(m);
  return t;
}
