/*
   Vec is a generic set of N named values of type T (aka. a mathematical vector)

   The values can be accessed via array index or their common component names:

      -  u,  v
      -  x,  y,  z,  w
      -  r,  g,  b,  a
      - c0, c1, c2, c3, ... cN

   For most use cases, opt for the named variants of this type:

      Vec2 : Vec(2, float)
      Vec3 : Vec(3, float)
      Vec4 : Vec(4, float)
      Quat : Vec(4, float)

   Sadly, Vec does *NOT* solve the problem of interfacing with
   external vector types (including Jai's 'Math' module).

   To fix this, create two helper macros:

      to_jai :: (v: Vec4) -> jmath.Vector4 #expand {
         return v.(jmath.Vector4,force);
      }
      to_jai :: (v: *Vec4) -> *jmath.Vector4 #expand {
         return v.(*jmath.Vector4);
      }
*/
Vec :: struct(N: int, T: Type)
#modify {
   info := T.(*Type_Info);
   return info.type == .INTEGER || info.type == .FLOAT, "Vec T must be a numeric type (int or float)";
} {
   #assert (N > 0) "Vec N cannot be <= 0";

   // Vecs are backed by an array internally. The #insert block below
   // generates #place'd unions of named fields or cN fields when N is > 4.
   #as components: [N]T;

   #insert -> string {
      b: basic.String_Builder;
      basic.append(*b, "#place components;\n");

      fields :: []string.[
         string.[ "x", "r", "u" ],
         string.[ "y", "g", "v" ],
         string.[ "z", "b", ""  ],
         string.[ "w", "a", ""  ],
      ];

      for i: 0..N - 1 {
         if i < fields.count {
            basic.append(*b, "union {\n");
            for field: fields[i] if field.count != 0 {
               basic.print_to_builder(*b, "\t%: T = ---;\n", field);
            }
            basic.print_to_builder(*b, "\tc%: T = ---;\n", i);
            basic.append(*b, "};\n");
         }
         else {
            basic.print_to_builder(*b, "c%: T = ---;\n", i);
         }
      }

      return basic.builder_to_string(*b);
   };
}

operator [] :: (v: Vec, $$idx: int) -> v.T #no_abc {
   bounds_check_index(idx, v.N);
   return v.components[idx];
}

operator *[] :: (v: *Vec, $$idx: int) -> *v.T #no_abc {
   bounds_check_index(idx, v.N);
   return *v.components[idx];
}

operator []= :: (v: *Vec, $$idx: int, value: v.T) #no_abc {
   bounds_check_index(idx, v.N);
   v.components[idx] = value;
}

for_expansion :: (v: *Vec, body: Code, flags: For_Flags) #expand {
   for i: 0..v.N - 1 {
      `it_index := i;
      #if flags & .POINTER {
         `it := *v.components[i];
      } else {
         `it := v.components[i];
      }

      #insert,scope(body) body;
   }
}

operator + :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
   res: Vec(l.N, l.T) = ---;
   // @todo(judah): unroll for N <= 4
   for l res[it_index] = it + r[it_index];
   return res;
}

operator - :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
   res: Vec(l.N, l.T) = ---;
   // @todo(judah): unroll for N <= 4
   for l res[it_index] = it - r[it_index];
   return res;
}

operator * :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
   res: Vec(l.N, l.T) = ---;
   // @todo(judah): unroll for N <= 4
   for l res[it_index] = it * r[it_index];
   return res;
}

operator / :: inline (l: Vec, r: Vec(l.N, l.T)) -> Vec(l.N, l.T) #no_abc {
   res: Vec(l.N, l.T) = ---;
   // @todo(judah): unroll for N <= 4
   for l res[it_index] = it / r[it_index];
   return res;
}

// Concrete vector types (the usual cases)

Vec2 :: Vec(2, float);
Vec3 :: Vec(3, float);
Vec4 :: Vec(4, float);
Quat :: Vec4;

v2f :: (x: $T = 0, y: T = 0) -> Vec2
#modify { return T.(*Type_Info).type == .FLOAT, "use v2i for integer arguments"; }
#expand {
   return .{ x = x, y = y };
}

v2i :: (x: $T = 0, y: T = 0) -> Vec(2, T)
#modify { return T.(*Type_Info).type == .INTEGER, "use v2f for float arguments"; }
#expand {
   return .{ x = x, y = y };
}

v3f :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec3
#modify { return T.(*Type_Info).type == .FLOAT, "use v3i for integer arguments"; }
#expand {
   return .{ x = x, y = y, z = z };
}

v3i :: (x: $T = 0, y: T = 0, z: T = 0) -> Vec(3, T)
#modify { return T.(*Type_Info).type == .INTEGER, "use v3f for float arguments"; }
#expand {
   return .{ x = x, y = y, z = z };
}

v4f :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec4
#modify { return T.(*Type_Info).type == .FLOAT, "use v4i for integer arguments"; }
#expand {
   return .{ x = x, y = y, z = z, w = w };
}

v4i :: (x: $T = 0, y: T = 0, z: T = 0, w: T = 0) -> Vec(4, T)
#modify { return T.(*Type_Info).type == .INTEGER, "use v4f for float arguments"; }
#expand {
   return .{ x = x, y = y, z = z, w = w };
}

quat :: (x: float = 0, y: float = 0, z: float = 0, w: float = 0) -> Quat #expand {
   return .{ x = x, y = y, z = z, w = w };
}


#if RUN_TESTS #run,stallable {
   test.run("vec2:ops", t => {
      a := v2f(10.0, 1);
      b := v2f(20.0, 2);
      c := a + b;

      test.expect(t, c.x == 30 && c.y == 3);
   });
}

#scope_file;

jx :: #import,file "../module.jai";
