/// JcMajor is the current major version of this library.
///
/// Major versions are guaranteed to have stable apis
/// for their duration.
JcMajor :: 0;

/// JcMinor is the current minor version of this library.
///
/// Minor versions denote bug fixes, additions, or improvements
/// that do not affect api stability.
JcMinor :: 1;

// @note(judah): we can't use range_of here because a compiler bug?

S8Min  :: #run min_of(s8);  /// -128 (-0x80)
S8Max  :: #run max_of(s8);  ///  127 (0x7f)
S16Min :: #run min_of(s16); /// -32768 (-0x8000)
S16Max :: #run max_of(s16); ///  32767 (0x7f_ff)
S32Min :: #run min_of(s32); /// -2147483648 (-0x8000_0000)
S32Max :: #run max_of(s32); ///  2147483647 (0x7fff_ffff)
S64Min :: #run min_of(s64); /// -9223372036854775808 (-0x80000000_00000000)
S64Max :: #run max_of(s64); ///  9223372036854775807 (0x7fffffff_ffffffff)

U8Min  :: #run min_of(u8);  /// 0 (0x00)
U8Max  :: #run max_of(u8);  /// 255 (0xff)
U16Min :: #run min_of(u16); /// 0 (0x00_00)
U16Max :: #run max_of(u16); /// 65535 (0xff_ff)
U32Min :: #run min_of(u32); /// 0 (0x0000_0000)
U32Max :: #run max_of(u32); /// 4294967295 (0xffff_ffff)
U64Min :: #run min_of(u64); /// 0 (0x00000000_00000000)
U64Max :: #run max_of(u64); /// 18446744073709551615 (0xffffffff_ffffffff)

Float32Min :: #run min_of(float32); /// 1.17549e-38 (0h0080_0000)
Float32Max :: #run max_of(float32); /// 3.40282e+38 (0h7f7fffff)
Float64Min :: #run min_of(float64); /// 2.22507e-308 (0h00100000_00000000)
Float64Max :: #run max_of(float64); /// 1.79769e+308 (0h7fefffff_ffffffff)

/// m0 is a 0-size marker type.
///
/// It allows specific offsets within a type to be marked which is useful for (de)serialization.
///
///    MyType :: struct {
///       do_not_serialize_1: *void;
///
///       _start: m0; // Has the same offset as serialize_1
///       serialize_1: [32]u8;
///       serialize_2: u64;
///       serialize_3: bool;
///       serialize_4: float32;
///       _end: m0; // Has the same offset as serialize_4
///
///       do_not_serialize_2: [..]int;
///    }
///
///    value := MyType.{};
///    start := *value + offset_of(value, #code _start);
///    end   := *value + offset_of(value, #code _end);
///    WriteToDisk(data = start, count = end - start);
///
m0 :: #type void;

b8  :: enum u8  { false_ :: (0 != 0).(u8);  true_ :: (0 == 0).(u8);  }; /// b8 is an 8-bit boolean.
b16 :: enum u16 { false_ :: (0 != 0).(u16); true_ :: (0 == 0).(u16); }; /// b16 is a 16-bit boolean.
b32 :: enum u32 { false_ :: (0 != 0).(u32); true_ :: (0 == 0).(u32); }; /// b32 is a 32-bit boolean.
b64 :: enum u64 { false_ :: (0 != 0).(u64); true_ :: (0 == 0).(u64); }; /// b64 is a 64-bit boolean.

#if size_of(int) == {
   case 8; uint :: u64;
   case 4; uint :: u32;
   case 2; uint :: u16;
   case 1; uint :: u8;
}

/// Panic displays the given message and crashes the program.
///
/// Note: Defers will not run when Panic is called.
Panic :: (message := "jc: runtime panic", loc := #caller_location) #expand #no_debug {
   #if DebugBuild {
      WriteStderrLocation(loc);
      WriteStderrString(": ");
   }

   WriteStderrString(message, "\n");

   #if DebugBuild {
      PrintStackTrace();
   }

   Trap();
}

/// Unreachable displays the given message and causes an execution trap.
///
/// Note: Defers will not run when Unreachable is called.
Unreachable :: (message := "jc: unreachable code hit", loc := #caller_location) #expand #no_debug {
   trap :: #ifx DebugBuild then DebugTrap else Trap;

   #if DebugBuild {
      WriteStderrLocation(loc);
      WriteStderrString(": ");
   }

   WriteStderrString(message, "\n");

   #if DebugBuild {
      PrintStackTrace();
   }

   Trap();
}

/// CompileError displays the given message and stops compilation.
///
/// Note: By default, the error is reported at the callsite.
CompileError :: (message: string, loc := #caller_location) #expand #no_debug #compile_time {
   if #compile_time {
      compiler_report(message, loc, .ERROR);
   }
   else {
      Panic("jc: CompileError can only be called at compile-time", loc = loc);
   }
}

// @todo(judah): these should be different!

/// Trap causes an execution trap.
Trap :: () #expand #no_debug {
   debug_break(); // Provided by Runtime_Support
}

/// DebugTrap causes an execution trap that grabs the attention of a debugger.
DebugTrap :: () #expand #no_debug {
   debug_break(); // Provided by Runtime_Support
}

#if DebugBuild
{
   /// Assert causes a debug break if the given condition is false.
   Assert :: (cond: bool, message := "jc: condition was false", loc := #caller_location) #expand #no_debug {
      // @note(judah): We only need to do this to route into the context's builtin assertion handling.
      if cond || context.handling_assertion_failure return;

      context.handling_assertion_failure = true;
      should_trap := context.assertion_failed(loc, message);
      context.handling_assertion_failure = false;

      if should_trap {
         DebugTrap();
      }
   }

   /// AssertCallsite works identically to Assert, except that it expects a
   /// Source_Code_Location (called 'loc') to exist in the calling scope.
   ///
   ///    MyProc :: (loc := #caller_location) {
   ///       AssertCallsite(false);    // 'loc' is passed implicitly
   ///       Assert(false, loc = loc); // equivalent
   ///    }
   ///
   AssertCallsite :: (cond: bool, message := "jc: condition was false") #expand #no_debug {
      Assert(cond, message, loc = `loc);
   }
}
else
{
   // @note(judah): these need to be separate declarations so we can use #discard.
   // otherwise, the compiler will generate instructions to setup the call when assertions are disabled.
   Assert         :: (#discard cond: bool, #discard message := "", #discard loc := #caller_location) #expand #no_debug {}
   AssertCallsite :: (#discard cond: bool, #discard message := "") #expand #no_debug {}
}


#scope_module

WriteString :: write_strings; @jc.nodocs // Provided by Runtime_Support
WriteNumber :: write_number;  @jc.nodocs // Provided by Runtime_Support

// @note(judah): This is a direct copy of Runtime_Support's write_loc since it's not exported
WriteStderrLocation :: (loc: Source_Code_Location) {
   WriteStderrString(loc.fully_pathed_filename, ":");
   WriteStderrNumber(loc.line_number);
   WriteStderrString(",");
   WriteStderrNumber(loc.character_number);
} @jc.nodocs

WriteStderrString :: #bake_arguments write_strings(to_standard_error = true); // Provided by Runtime_Support
WriteStderrNumber :: #bake_arguments write_number(to_standard_error  = true); // Provided by Runtime_Support


#scope_file

DebugBuild :: #run -> bool {
   // @note(judah): there's not really a good way to detect opt level/build type,
   // so just check if debug info is being emitted.
   #import "Compiler";
   opts := get_build_options();
   return opts.emit_debug_info != .NONE;
};

PrintStackTrace :: () {
   WriteStderrString("\n----- Stack Trace -----\n");

   node := context.stack_trace.next;
   while node != null {
      loc := node.info.location;
      loc.line_number = node.line_number;
      WriteStderrLocation(loc);

      name := node.info.name;
      if name.count <= 0
       { name = "(unknown)"; }
      WriteStderrString(": in ", name, "\n");

      if node.next != null
       { node = node.next; }
   }
}
