/*
   A module that hijacks the build process of your project
   to enable easy hot-reloading.

   See: 'examples/everything-you-need.jai' for how this
   module is intended to be used.
*/
#module_parameters(disabled := false, loc := #caller_location);

Status :: enum {
   none;
   quit;
   soft_reload;
   hard_reload;
}

Simple_Build_Options :: struct {
   output_name:          string;
   relative_output_path: string;
}

Init_Proc          :: #type (state: *void, allocator: Allocator, full_reset: bool);
Setup_Proc         :: #type ();
Teardown_Proc      :: #type ();
Frame_Proc         :: #type () -> Status;
Build_Options_Proc :: #type () -> Simple_Build_Options;

#scope_module;

#placeholder frame;
#placeholder init;
#placeholder setup;
#placeholder teardown;
#placeholder build_options;

#if !disabled {
   #load "reload_main.jai";
}

// Ensure laptops use the higher performance GPU on Windows.
#if OS == .WINDOWS {
   /*
   https://docs.nvidia.com/gameworks/content/technologies/desktop/optimus.htm

   Starting with the Release 302 drivers, application developers can direct the Optimus driver at runtime to use the High Performance Graphics to render any application —- even those applications for which there is no existing application profile. They can do this by exporting a global variable named NvOptimusEnablement. The Optimus driver looks for the existence and value of the export. Only the LSB of the DWORD matters at this time. A value of 0x00000001 indicates that rendering should be performed using High Performance Graphics. A value of 0x00000000 indicates that this method should be ignored.
   */
   #program_export NvOptimusEnablement: u32 = 0x00000001;

   /*
   https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/

   Many gaming and workstation laptops are available with both (1) integrated power saving and (2) discrete high performance graphics devices. Unfortunately, 3D intensive application performance may suffer greatly if the best graphics device is not selected. For example, a game may run at 30 Frames Per Second (FPS) on the integrated GPU rather than the 60 FPS the discrete GPU would enable. As a developer you can easily fix this problem by adding only one line to your executable's source code:
   */
   #program_export AmdPowerXpressRequestHighPerformance: u32 = 0x00000001;
}


#scope_module;

#if !disabled {
   #assert (type_of(init)          == Init_Proc) "init was the wrong type!";
   #assert (type_of(setup)         == Setup_Proc) "setup was the wrong type!";
   #assert (type_of(teardown)      == Teardown_Proc) "teardown was the wrong type!";
   #assert (type_of(frame)         == Frame_Proc) "frame was the wrong type!";
   #assert (type_of(build_options) == Build_Options_Proc) "build_options was the wrong type!";
}

file_path    :: #run loc.fully_pathed_filename;
proj_path    :: #run path_strip_filename(file_path);
temp_path    :: #run tprint("%/.build/tmp.jai", proj_path);
temp_exists  :: #run file_exists(temp_path);
lib_name     :: #run tprint("%_lib", user_options.output_name);

user_options :: #run -> Simple_Build_Options {
   opts := build_options();

   if opts.relative_output_path.count == 0 {
      opts.relative_output_path = proj_path;
   } else {
      opts.relative_output_path = tprint("%/%", proj_path, opts.relative_output_path);
   }

   if opts.output_name.count == 0 {
      opts.output_name = path_strip_extension(path_filename(file_path));
   }

   return opts;
}

// only run the build when we the module importing us is compiled
#if !(disabled || temp_exists) #run {
   set_build_options_dc(.{ do_output = false });

   make_directory_if_it_does_not_exist(".build");

   // build host executable
   {
      ws := compiler_create_workspace(file_path);
      defer compiler_destroy_workspace(ws);

      opts := get_build_options(ws);
      opts.text_output_flags = 0;
      opts.output_path = user_options.relative_output_path;
      opts.output_type = .EXECUTABLE;
      opts.output_executable_name = user_options.output_name;

      // create a temporary file that contains the hijacked main.
      // we need to #load our program library so the #placeholders
      // are filled.

      tmp: String_Builder;
      print_to_builder(*tmp, #string END
         #load "%1";

         main :: () {
            (#import "jx/reload").reload_main();
         }
      END, file_path);

      assert(write_entire_file(temp_path, builder_to_string(*tmp)));

      compiler_begin_intercept(ws);
      set_build_options(opts, ws);

      add_build_file(temp_path, ws);
      while true {
         msg := compiler_wait_for_message();
         if msg.kind == .COMPLETE || msg.kind == .ERROR break;
      }
      compiler_end_intercept(ws);
   }

   // build program as dynamic library
   {
      ws := compiler_create_workspace(file_path);
      defer compiler_destroy_workspace(ws);

      opts := get_build_options(ws);
      opts.output_path = user_options.relative_output_path;
      opts.output_type = .DYNAMIC_LIBRARY;
      opts.output_executable_name = lib_name;

      compiler_begin_intercept(ws);
      set_build_options(opts, ws);

      add_build_file(temp_path, ws);
      while true {
         msg := compiler_wait_for_message();
         if msg.kind == .COMPLETE || msg.kind == .ERROR break;
      }
      compiler_end_intercept(ws);
   }

   file_delete(temp_path);
}

#import "Basic";
#import "File";
#import "File_Utilities";
#import "String";
#import "Compiler";
