// These return the bool first so you can check in a conditional,
// rather than having to do '_, ok := ...'

check_type_tag :: ($$T: Type, tag: Type_Info_Tag) -> bool, *Type_Info {
   #if is_constant(T) {
      info :: type_info(T);
      if info.type == tag return true, info;
   }
   else {
      info := T.(*Type_Info);
      if info.type == tag return true, info;
   }

   return false, null;
}

type_is_integer :: ($$T: Type) -> bool, *Type_Info_Integer {
   ok, info := check_type_tag(T, .INTEGER);
   return ok, info.(*Type_Info_Integer);
}

type_is_float :: ($$T: Type) -> bool, *Type_Info_Float {
   ok, info := check_type_tag(T, .FLOAT);
   return ok, info.(*Type_Info_Float);
}

type_is_scalar :: (t: Type) -> bool {
   return type_is_integer(t) || type_is_float(t);
}

type_is_array :: ($$T: Type) -> bool, *Type_Info_Array {
   ok, info := check_type_tag(T, .ARRAY);
   return ok, info.(*Type_Info_Array);
}

type_is_struct :: ($$T: Type) -> bool, *Type_Info_Struct {
   ok, info := check_type_tag(T, .STRUCT);
   return ok, info.(*Type_Info_Struct);
}

type_is_enum :: ($$T: Type) -> bool, *Type_Info_Enum {
   ok, info := check_type_tag(T, .ENUM);
   return ok, info.(*Type_Info_Enum);
}

// Returns the lowest and highest values T can represent.
// Note: T must be an integer, float, or enum type.
range_for :: ($T: Type, loc := #caller_location) -> (T, T) #expand {
   // @note(judah): we need to runs here because jai is weird.
   return #run lo_for(T, loc = loc), #run hi_for(T, loc = loc);
}

// Returns the lowest value T can represent.
// Note: T must be an integer, float, or enum type.
lo_for :: ($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;
                  compiler.compiler_report("unhandled integer type", loc = loc);
            }

         case .FLOAT;
            if info.runtime_size == {
               case 4; return (0h00800000).(T, no_check);
               case 8; return (0h00100000_00000000).(T, no_check);
               case;
                  compiler.compiler_report("unhandled float type", 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;
            compiler.compiler_report("min requires an enum, integer, or float type", loc = loc);
      }

      return 0;
   };
}

// Returns the highest value T can represent.
// Note: T must be an integer, float, or enum type.
hi_for :: ($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;
                  compiler.compiler_report("unhandled integer type", loc = loc);
            }

         case .FLOAT;
            if info.runtime_size == {
               case 4; return (0h7F7FFFFF).(T, no_check);
               case 8; return (0h7FEFFFFF_FFFFFFFF).(T, no_check);
               case;
                  compiler.compiler_report("unhandled float type", 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;
            compiler.compiler_report("max requires an enum, integer, or float type", loc = loc);
      }

      return 0;
   };
}


#scope_file;

compiler :: #import "Compiler"; // @future


// ----------------------------------------------------------
// TESTS
// ----------------------------------------------------------

#if RUN_TESTS #run {
   test.run("lo_for:primitives", t => {
      test.expect(t, lo_for(u8)  == 0);
      test.expect(t, lo_for(s8)  == -128);
      test.expect(t, lo_for(u16) == 0);
      test.expect(t, lo_for(s16) == -32768);
      test.expect(t, lo_for(u32) == 0);
      test.expect(t, lo_for(s32) == -2147483648);
      test.expect(t, lo_for(u64) == 0);
      test.expect(t, lo_for(s64) == -9223372036854775808);

      test.expect(t, lo_for(float32) == 0h00800000);
      test.expect(t, lo_for(float64) == 0h00100000_00000000);
   });

   test.run("hi_for:primitives", t => {
      test.expect(t, hi_for(u8)  == 255);
      test.expect(t, hi_for(s8)  == 127);
      test.expect(t, hi_for(u16) == 65535);
      test.expect(t, hi_for(s16) == 32767);
      test.expect(t, hi_for(u32) == 4294967295);
      test.expect(t, hi_for(s32) == 2147483647);
      test.expect(t, hi_for(u64) == 18446744073709551615);
      test.expect(t, hi_for(s64) == 9223372036854775807);

      test.expect(t, hi_for(float32) == 340282346638528859000000000000000000000.0);
      test.expect(t, hi_for(float64) == 179769313486231570900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0);
   });

   test.run("lo_for/hi_for:enums", t => {
      U8_Enum :: enum u8 { lo :: -1; hi :: +1; }
      S8_Enum :: enum s8 { lo :: -2; hi :: -1; }
      {
         test.expect(t, lo_for(U8_Enum) == U8_Enum.lo);
         test.expect(t, lo_for(S8_Enum) == S8_Enum.lo);
         test.expect(t, hi_for(U8_Enum) == U8_Enum.hi);
         test.expect(t, hi_for(S8_Enum) == S8_Enum.hi);
      }

      U16_Enum :: enum u16 { lo :: -1; hi :: +1; }
      S16_Enum :: enum s16 { lo :: -2; hi :: -1; }
      {
         test.expect(t, lo_for(U16_Enum) == U16_Enum.lo);
         test.expect(t, lo_for(S16_Enum) == S16_Enum.lo);
         test.expect(t, hi_for(U16_Enum) == U16_Enum.hi);
         test.expect(t, hi_for(S16_Enum) == S16_Enum.hi);
      }

      U32_Enum :: enum u32 { lo :: -1; hi :: +1; }
      S32_Enum :: enum s32 { lo :: -2; hi :: -1; }
      {
         test.expect(t, lo_for(U32_Enum) == U32_Enum.lo);
         test.expect(t, lo_for(S32_Enum) == S32_Enum.lo);
         test.expect(t, hi_for(U32_Enum) == U32_Enum.hi);
         test.expect(t, hi_for(S32_Enum) == S32_Enum.hi);
      }

      U64_Enum :: enum u64 { lo :: -1; hi :: +1; }
      S64_Enum :: enum s64 { lo :: -2; hi :: -1; }
      {
         test.expect(t, lo_for(U64_Enum) == U64_Enum.lo);
         test.expect(t, lo_for(S64_Enum) == S64_Enum.lo);
         test.expect(t, hi_for(U64_Enum) == U64_Enum.hi);
         test.expect(t, hi_for(S64_Enum) == S64_Enum.hi);
      }

      // @note(judah): just making sure this compiles
      lo, hi := range_for(U64_Enum);
      test.expect(t, lo == U64_Enum.lo);
      test.expect(t, hi == U64_Enum.hi);
   });
}
