/// offset_of returns the byte offset of a field within the type T.
///
/// Note: T must be a struct type.
///
///     MyType :: struct { x: int; y: int; z: int; };
///     offset_of(MyType, #code y); // 8
///
offset_of :: ($T: Type, ident: Code, loc := #caller_location) -> int #expand {
   #run (loc: Source_Code_Location) {
      if !TypeIsStruct(T) {
         CompileError("jc: offset_of can only be used on struct types", loc = loc);
      }
   }(loc);

   return #run -> int {
      t: T = ---;
      return (*t.#insert ident).(*void) - (*t).(*void);
   };
}

/// offset_of returns the byte offset of a field within the type of value.
///
/// Note: If offset_of is given a pointer value, it will use the type pointed to.
///
///     value := struct{ x: int; y: int; z: int; }.{};
///     offset_of(value, #code y); // 8
///
offset_of :: (#discard value: $T, ident: Code, loc := #caller_location) -> int #expand {
   type :: #run -> Type {
      info := T.(*Type_Info);

      ok, pinfo := TypeIsPointer(T);
      if ok {
         // @question(judah): do we want it to traverse all the way up to a non-pointer type?
         // I opted against because if you have a *T, you only want offset_of to get an offset
         // from that pointer. What would you do with a field offset from **T?
         if TypeIsPointer(pinfo.pointer_to) {
            CompileError("jc: offset_of only allows one level of pointer indirection.", loc = loc);
         }

         info = pinfo.pointer_to;
      }

      return get_type(info);
   };

   return offset_of(type, ident, loc = loc);
}

/// align_of returns the alignment of type T.
align_of :: ($T: Type) -> int #expand {
   return #run -> int {
      if size_of(T) == 0
       { return 0; }
      return offset_of(struct{ _: u8; t: T; }, #code t);
   };
}

/// default_of returns a value of type T as if it was just instantiated.
///
/// Note: default_of will call the initializer for aggregate types, so you
/// may want zero_of instead.
default_of :: ($T: Type) -> T #expand {
   default: T;
   return default;
}

/// undefined_of returns a value of type T that has not been initialized.
undefined_of :: ($T: Type) -> T #expand {
   uninit: T = ---;
   return uninit;
}

/// zero_of returns a value of type T that has been zero-initialized.
///
/// Note: zero_of will not call the initializer for aggregate types, so you
/// may want default_of instead.
zero_of :: ($T: Type) -> T #expand {
   zero := undefined_of(T);
   MemZero(*zero);
   return zero;
}

/// min_of returns the minimum value T can represent.
///
/// Note: T must be an integer, float, or enum type.
min_of :: ($T: Type, loc := #caller_location) -> T #expand {
   return #run -> T {
      info := T.(*Type_Info);
      if info.type == {
         case .INTEGER;
            i := info.(*Type_Info_Integer);
            if i.runtime_size == {
               case 1; return (ifx i.signed then -0x80                  else 0).(T, no_check);
               case 2; return (ifx i.signed then -0x8000                else 0).(T, no_check);
               case 4; return (ifx i.signed then -0x8000_0000           else 0).(T, no_check);
               case 8; return (ifx i.signed then -0x8000_0000_0000_0000 else 0).(T, no_check);
               case  ; CompileError("jc: unknown integer size", loc = loc);
            }

         case .FLOAT;
            if info.runtime_size == {
               case 4; return (0h0080_0000).(T, no_check);
               case 8; return (0h00100000_00000000).(T, no_check);
               case  ; CompileError("jc: unknown float size", loc = loc);
            }

         case .ENUM;
            i := info.(*Type_Info_Enum);
            if i.values.count == 0 {
               return 0;
            }

            min: T = i.values[0].(T, no_check);
            if i.internal_type.signed {
               for i.values if it.(T) < min {
                  min = it.(T);
               }
            }
            else {
               for i.values if it.(T) < min {
                  min = it.(T);
               }
            }

            return min;

         case;
            CompileError("jc: min_of requires an enum, integer, or float type", loc = loc);
      }

      return 0;
   };
}

/// max_of returns the maximum value T can represent.
///
/// Note: T must be an integer, float, or enum type.
max_of :: ($T: Type, loc := #caller_location) -> T #expand {
   return #run -> T {
      info := T.(*Type_Info);
      if info.type == {
         case .INTEGER;
            i := info.(*Type_Info_Integer);
            if i.runtime_size == {
               case 1; return (ifx i.signed then 0x7f                  else 0xff).(T, no_check);
               case 2; return (ifx i.signed then 0x7fff                else 0xffff).(T, no_check);
               case 4; return (ifx i.signed then 0x7fff_ffff           else 0xffff_ffff).(T, no_check);
               case 8; return (ifx i.signed then 0x7fff_ffff_ffff_ffff else 0xffff_ffff_ffff_ffff).(T, no_check);
               case  ; CompileError("jc: unknown integer size", loc = loc);
            }

         case .FLOAT;
            if info.runtime_size == {
               case 4; return (0h7F7FFFFF).(T, no_check);
               case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
               case  ; CompileError("jc: unknown float size", loc = loc);
            }

         case .ENUM;
            i := info.(*Type_Info_Enum);
            if i.values.count == 0 {
               return 0;
            }

            max := i.values[0].(T, no_check);
            if i.internal_type.signed {
               for i.values if xx it > max {
                  max = xx it;
               }
            }
            else {
               for i.values if xx it > max {
                  max = xx it;
               }
            }

            return max;

         case;
            CompileError("jc: max_of requires an enum, integer, or float type", loc = loc);
      }

      return 0;
   };
}

/// range_of returns the minimum and maximum values T can represent.
///
/// Note: T must be an integer, float, or enum type.
range_of :: ($T: Type, loc := #caller_location) -> (T, T) #expand {
   return min_of(T, loc = loc), max_of(T, loc = loc);
}

