Rect :: #type,distinct Vec(4, RECT_TYPE);

Circle :: struct {
   x,y,r: float;
   #place x;
   pos: Vec2;
}

Line :: struct {
   x0,y0,x1,y1: float;
   #place x0;
   a: Vec2;
   #place x1;
   b: Vec2;
   #place x0;
   e: [2]Vec2;
}

Triangle :: [3]Vec2;

ORIGIN :: Vec2.{.[0, 0]};

make_rect :: (x: RECT_TYPE, y: RECT_TYPE, w: RECT_TYPE, h: RECT_TYPE) -> Rect {
   r: Rect = ---;
   r.x = x;
   r.y = y;
   r.width = w;
   r.height = h;
   return r;
}

cut_left :: (rect: *Rect, want: RECT_TYPE) -> Rect {
   amnt := basic.min(want, rect.width);
   r := rect.*;
   r.width = amnt;
   rect.x += amnt;
   rect.width -= amnt;
   return r;
}

cut_right :: (rect: *Rect, want: RECT_TYPE) -> Rect {
   amnt := basic.min(want, rect.width);
   r := rect.*;
   r.x += rect.width - amnt;
   r.width = amnt;
   rect.width -= r.width;
   return r;
}

cut_top :: (rect: *Rect, want: RECT_TYPE) -> Rect {
   amnt := basic.min(rect.height, want);
   r := rect.*;
   r.height -= amnt;
   rect.y += amnt;
   rect.height -= amnt;
   return r;
}

cut_bottom :: (rect: *Rect, want: RECT_TYPE) -> Rect {
   amnt := basic.min(want, rect.height);
   r := rect.*;
   r.y += r.height - amnt;
   r.height = amnt;
   rect.height -= amnt;
   return r;
}

area :: (r: Rect) -> float {
   return r.width*r.height;
}

area :: (c: Circle) -> float {
   return PI*c.r*c.r;
}

inside :: (r: Rect, p: Vec2) -> bool #symmetric {
   return p.x >= r.x && p.x <= r.x + r.width &&
          p.y >= r.y && p.y <= r.y + r.height;
}

inside :: (c: Circle, p: Vec2) -> bool #symmetric {
   dp := p - c.pos;
   return dp.x*dp.x + dp.y*dp.y <= c.r*c.r;
}

inside :: (t: Triangle, p: Vec2) -> bool #symmetric {
   cay := t[2].y - t[0].y;
   pay := p.y - t[0].y;
   cax := t[2].x - t[0].x;
   bay := t[1].y - t[0].y;
   bax := t[1].x - t[0].x;

   denom := bay*cax - bax*cay;
   w1 := t[0].x*cay + pay*cax - p.x*cay;
   w1 /= denom;
   w2 := (p.y - t[0].y - w1*bay)/cay;
   return w1 >= 0 && w2 >= 0 && w1 + w2 <= 1;
}

collides :: (c1: Circle, c2: Circle) -> bool {
   dp := c2.pos - c1.pos;
   return c1.r + c2.r >= length(dp);
}

// Note(Jesse): This is using sdfs. Very elegant
collides :: (c: Circle, r: Rect) -> bool #symmetric {
   // We need to 'transpose' all the math to the origin the center of the circle acting as the origin (0, 0)
   r_center: Vec2 = get_center(r);
   p := r_center - c.pos;
   d: Vec2 = abs(p) - Vec2.{.[r.width/2, r.height/2]};
   dist := length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
   dist -= c.r; // 'adding' the distance of the circle
   return dist <= 0.0;
}

// Note(Jesse): Minkowski difference but instead of the origin we check if the other rectangle center
collides :: (r1: Rect, r2: Rect) -> bool {
   r: Rect = Rect.{.[r1.x - r2.width/2, r1.y - r2.width/2, r1.width + r2.width, r1.height + r2.height]};
   return inside(r, get_center(r2));
}

collides :: (c: Circle, seg: Line) -> bool #symmetric {
   pa := c.pos - seg.a;
   ba := seg.b - seg.a;
   // Note(Jesse): Using clamp here will cause small segments to collide even when they don't
   h: float = min(max(dot(pa,ba)/dot(ba,ba), 0.0), 1.0);
   dist := length(pa - ba*h);
   return dist - c.r <= 0.0;
}

collides :: (s1: Line, s2: Line) -> bool {
   denom := ((s2.y1 - s2.y0)*(s1.x1 - s1.x0) - (s2.x1 - s2.x0)*(s1.y1 - s1.y0));

   a := ((s2.x1 - s2.x0)*(s1.y0 - s2.y0) - (s2.y1 - s2.y0)*(s1.x0 - s2.x0));
   b := ((s1.x1 - s1.x0)*(s1.y0 - s2.y0) - (s1.y1 - s1.y0)*(s1.x0 - s2.x0));

   if denom == 0.0
      return false;

   a /= denom;
   b /= denom;

   return (a >= 0 && a <= 1) && (b >= 0 && b <= 1);
}

// Cohen-sutherland's line clipping algorithm
collides :: (r: Rect, l: Line) -> bool #symmetric {
   INSIDE :: 0b0000;
   LEFT   :: 0b0001;
   RIGHT  :: 0b0010;
   BOTTOM :: 0b0100;
   TOP    :: 0b1000;

   compute_out_code :: (x: float, y: float) -> int #expand {
      code := INSIDE;
      if x < xmin
         code |= LEFT;
      else if x > xmax
         code |= RIGHT;
      if y < ymin
         code |= BOTTOM;
      else if y > ymax
         code |= TOP;
      return code;
   }

   xmin := r.x;
   xmax := r.x + r.width;
   ymin := r.y;
   ymax := r.y + r.height;

   x0 := l.a.x;
   y0 := l.a.y;
   x1 := l.b.x;
   y1 := l.b.y;

   oc0 := compute_out_code(l.a.x, l.a.y);
   oc1 := compute_out_code(l.b.x, l.b.y);
   accept: bool;
   while true {
      if !(oc0 | oc1) {
         accept = true;
         break;
      }
      else if oc0 & oc1 {
         break;
      }
      else {
         x, y: float = ---;
         oc_out := ifx oc1 > oc0 then oc1 else oc0;

         // find intersection point;
         // slope = (y1 - y0)/(x1 - x0)
         // x = x0 + (1/slope)*(ym - y0), ym is ymin or ymax
         // y = y0 + slope*(xm - x0), xm is xmin or xmax
         // outcode bit guarantees the denom is non-zero
         if oc_out & TOP {
            x = x0 + (x1 - x0)*(ymax - y0)/(y1 - y0);
            y = ymax;
         }
         else if oc_out & BOTTOM {
            x = x0 + (x1 - x0)*(ymin - y0)/(y1 - y0);
            y = ymin;
         }
         else if oc_out & RIGHT {
            y = y0 + (y1 - y0)*(xmax - x0)/(x1 - x0);
            x = xmax;
         }
         else {
            y = y0 + (y1 - y0)*(xmin - x0)/(x1 - x0);
            x = xmin;
         }

         // Move outside points to intersection point
         if oc_out == oc0 {
            x0 = x;
            y0 = y;
            oc0 = compute_out_code(x0, y0);
         }
         else {
            x1 = x;
            y1 = y;
            oc1 = compute_out_code(x1, y1);
         }
      }
   }
   return accept;
}

// Triangle SDF with the circle position inplace of the origin and a circle expansion of the sdf
collides :: (t: Triangle, c: Circle) -> bool #symmetric {
   e0 := t[1] - t[0];
   e1 := t[2] - t[1];
   e2 := t[0] - t[2];
   v0 := c.pos - t[0];
   v1 := c.pos - t[1];
   v2 := c.pos - t[2];
   pq0 := v0 - e0*min(max(dot(v0,e0)/dot(e0,e0), 0.0), 1.0);
   pq1 := v1 - e1*min(max(dot(v1,e1)/dot(e1,e1), 0.0), 1.0);
   pq2 := v2 - e2*min(max(dot(v2,e2)/dot(e2,e2), 0.0), 1.0);
   s := sign(e0.x*e2.y - e0.y*e2.x);
   d := min(min(Vec2.{.[dot(pq0,pq0), s*(v0.x*e0.y - v0.y*e0.x)]},
           Vec2.{.[dot(pq1,pq1), s*(v1.x*e1.y - v1.y*e1.x)]}),
           Vec2.{.[dot(pq2,pq2), s*(v2.x*e2.y - v2.y*e2.x)]});
   dist := -math.sqrt(d.x)*sign(d.y);
   return dist < c.r;
}

collides :: (t: Triangle, r: Rect) -> bool #symmetric {
   p1 := Vec2.{.[r.x,           r.y]};
   p2 := Vec2.{.[r.x + r.width, r.y]};
   p3 := Vec2.{.[r.x,           r.y + r.height]};
   p4 := Vec2.{.[r.x + r.width, r.y + r.height]};

   rt1: Triangle = .[p1, p4, p2];
   rt2: Triangle = .[p1, p3, p4];

   if collides(t, rt1) || collides(t, rt2)
      return true;
   return false;
}

collides :: (t1: Triangle, t2: Triangle) -> bool {
   return inline gjk(t1, t2);
}

collides :: (t: Triangle, l: Line) -> bool #symmetric {
   return inline gjk(t, l.e);
}

// Note(Jesse): 2D
gjk :: (s1: []Vec2, s2: []Vec2) -> bool {
   S: Simplex2D;
   S.a = gjk_support(s1, s2, s1[0] - s2[0]);
   d := -S.a; // This makes a vector from S.a towards the origin (ORIGIN - S.a)
   S.points = 1;
   while true {
      A := gjk_support(s1, s2, d);
      if !same_dir(A, d) // If A is even towards the origin. Otherwise there's no way they intersect
         return false;
      S.points += 1;
      S.c = S.b; // we want S.a to be the newest point. S.c is the oldest
      S.b = S.a;
      S.a = A;
      if gjk_do_simplex(*S, *d)
         return true;
   }
   return false;
}

#scope_file

basic :: #import "Basic";

// Used with gjk, not for general use
Simplex2D :: struct {
   a,b,c: Vec2;
   points: int;
}

// Used with gjk. Simplified for 2D
triple_cross :: inline (a: Vec2, b: Vec2, c: Vec2) -> Vec2 {
   // return cross(cross(v3(a, 0), v3(b, 0)), v3(c, 0)).xy;
   return Vec2.{.[-a.x*b.y*c.y + a.y*b.x*c.y, a.x*b.y*c.x - a.y*b.x*c.x]};
}

// Sets the direction for the next support function based on the simplex we have and how it's orientated
// to the origin: 0, 0. Because we know which point was added last (S.a) we can deduce directions we don't
// have to check
gjk_do_simplex :: (S: *Simplex2D, dir: *Vec2) -> bool {
   if S.points == {
      case 2;
         AB := S.b - S.a;
         AO := -S.a;
         if same_dir(AB, AO) {
            dir.* = triple_cross(AB, AO, AB);
         }
         else {
            dir.* = AO;
            S.points = 1;
         }
      case 3;
         AO := -S.a;
         // Because this is 2D, we don't need a large 3 point simplex case, or a 4 point case
         AB := S.b - S.a;
         AC := S.c - S.a;
         abperp := triple_cross(AC, AB, AB);
         acperp := triple_cross(AB, AC, AC);
         if same_dir(abperp, AO) {
            S.points = 2;
            dir.* = abperp;
         }
         else if same_dir(acperp, AO) {
            S.b = S.c;
            S.points = 2;
            dir.* = acperp;
         }
         else {
            return true; // origin must be inside the triangle
         }
  }
  return false;
}

// Returns the vertex furthest along the dir vector for two polygons. We could split it up
// to take something that can't be represented by a list of points, like a circle.
gjk_support :: inline (a: []Vec2, b: []Vec2, dir: Vec2) -> Vec2 {
   helper :: (t: []Vec2, dir: Vec2) -> Vec2 {
      p := t[0];
      best := dot(t[0], dir);
      for 1..t.count - 1 {
         v := t[it];
         if dot(t[it], dir) > best {
            best = dot(v, dir);
            p = v;
         }
      }
      return p;
   }

   v := helper(a,  dir);
   w := helper(b, -dir);
   return v - w;
}

///////
// Helpers and operators

get_center :: (r: Rect) -> Vec2 {
   return v2f(r.x + r.width/2, r.y + r.height/2);
}

#if #exists(RUN_TESTS) #run {
   // Just a formality to get proper compile errors

   b: bool;

   r1 := Rect.{.[100, 150, 250, 250]};
   c1 := Circle.{150, 500, 50};
   l1 := Line.{400, 100, 650, 150};
   t1: Triangle;
   t1[0] = v2f(600.0, 500.0);
   t1[1] = v2f(650.0, 250.0);
   t1[2] = v2f(625.0, 200.0);

   p: Vec2 = v2f(100.0, 100.0);
   r2 := Rect.{.[100, 100, 10, 10]};
   c2 := Circle.{100, 100, 10};
   l2 := Line.{100, 100, 200, 200};
   t2: Triangle;
   t2[0] = v2f(50.0,      50.0 + 20.0);
   t2[1] = v2f(50.0 + 20.0, 50.0 - 20.0);
   t2[2] = v2f(50.0 - 20.0, 50.0 - 20.0);

   b |= inside(r1, p);
   b |= collides(r1, r2);
   b |= collides(r1, l2);
   b |= collides(r1, c2);
   b |= collides(r1, t2);

   b |= inside(c1, p);
   b |= collides(c1, r2);
   b |= collides(c1, l2);
   b |= collides(c1, c2);
   b |= collides(c1, t2);

   b |= collides(l1, r2);
   b |= collides(l1, l2);
   b |= collides(l1, c2);
   b |= collides(l1, t2);

   b |= inside(t1, p);
   b |= collides(t1, r2);
   b |= collides(t1, l2);
   b |= collides(t1, c2);
   b |= collides(t1, t2);
}
