#scope_file;

LOWERCASE_FIELD_NAMES :: true;

#run {
   set_build_options_dc(.{ do_output = false });

   files_to_generate :: string.[
      "radians",
      "degrees",
      "turns",
   ];

   for files_to_generate {
      print("building library for unit: %\n", it);

      assert(build(it, dynamic = true, simd = true), "failed to build library, unit: %", it);
      assert(build(it, dynamic = true, simd = false), "failed to build library, unit: %", it);
      assert(build(it, dynamic = false, simd = false), "failed to build library, unit: %", it);
      assert(build(it, dynamic = false, simd = true), "failed to build library, unit: %", it);
   }

   print("generating bindings\n");
   assert(generate(simd = true), "failed to generate bindings");
   assert(generate(simd = false), "failed to generate bindings");
}

build :: (unit: string, dynamic: bool, simd: bool) -> bool {
   #if OS == {
      case .WINDOWS;
         export_attribute :: "__declspec(dllexport)";
      case;
         export_attribute :: "__attribute__((visibility(\"default\")))";
   }

   tmp_data := tprint(#string END
      #define static
      #define inline
      #define COVERAGE(a, b) %1

      #define HANDMADE_MATH_USE_%2
      %3
      #include "../HandmadeMath.h"
   END,
      export_attribute,
      to_upper_copy(unit),
      ifx !simd then "#define HANDMADE_MATH_NO_SIMD" else "",
   );

   tmp_file := tprint(".build/tmp_%.c", ifx simd then "simd" else "nosimd");
   if !write_entire_file(tmp_file, tmp_data) return false;

   #if OS == {
      case .WINDOWS; out_base :: "win";
      case .MACOS;   out_base :: "mac";
      case .LINUX;   out_base :: "linux";
   }

   lib_name := tprint("hmm_%", unit);
   if simd lib_name = tprint("%_simd", lib_name);
   else    lib_name = tprint("%_nosimd", lib_name);

   out_path := tprint("%/%", out_base, lib_name);

   lib_type := Build_Type.STATIC_LIBRARY;
   if dynamic lib_type = .DYNAMIC_LIBRARY;

   return build_cpp(out_path, tmp_file, type = lib_type);
}

generate :: (simd: bool) -> bool {
   #if OS == {
      case .WINDOWS;
         lib_ext :: "dll";
      case .MACOS;
         lib_ext :: "dylib";
      case .LINUX;
         lib_ext :: "so";
   }

   #if OS == {
      case .WINDOWS; out_base :: "win";
      case .MACOS;   out_base :: "mac";
      case .LINUX;   out_base :: "linux";
   }

   suffix := "simd";
   if !simd suffix = "nosimd";

   simd_support = simd;

   opts: Generate_Bindings_Options;
   opts.add_generator_command = false;
   opts.generate_library_declarations = false;
   opts.visitor = fixup_wrong_types;
   array_add(*opts.strip_prefixes, "HMM_", "_HMM_");
   array_add(*opts.extra_clang_arguments, "-x", "c");
   array_add(*opts.libpaths, out_base);
   array_add(*opts.libnames, tprint("hmm_radians_%.%", suffix, lib_ext));
   array_add(*opts.source_files, tprint(".build/tmp_%.c", suffix));

   if !generate_bindings(opts, tprint("hmm_%.jai", suffix)) {
      return false;
   }

   return true;
}


simd_support := false;
fixup_wrong_types :: (decl: *Declaration, parent: *Declaration) -> Declaration_Visit_Result {
   if decl.kind == {
      case .TYPEDEF;
         td := decl.(*Typedef);
         if td.name == {
            // when simd support is specified, these are generated incorrectly so we create them manually
            case "HMM_Vec4"; #through;
            case "HMM_Quat";
               if simd_support {
                  td.decl_flags = .OMIT_FROM_OUTPUT;
                  td.type.type_of_struct.decl_flags = .OMIT_FROM_OUTPUT;
               }
         }

         #if !LOWERCASE_FIELD_NAMES return .STOP;

         lower_names_recursive :: (s: *Struct) {
            for s.declarations {
               it.output_name = pascal_to_snake(it.name);
               if it.type.type_of_struct != null {
                  lower_names_recursive(it.type.type_of_struct);
               }
            }
         }


         if td.type.type_of_struct != null {
            lower_names_recursive(td.type.type_of_struct);
         }

         return .RECURSE;

      case .FUNCTION;
         func := decl.(*Function);
         if func.name == {
            case "__hmm_invalid_generic";
               func.decl_flags = .OMIT_FROM_OUTPUT;
               return .STOP;
         }

         // snake_case argument names
         funct := func.type.type_of_function;
         for funct.arguments {
            it.output_name = pascal_to_snake(it.name);
         }
   }

   return .STOP;
}

pascal_to_snake :: (name: string) -> string {
   is_upper :: (byte: u8) -> bool {
     return byte >= #char "A" && byte <= #char "Z";
   }

   b: String_Builder;
   add_underscore := false;
   for 0..name.count - 1 {
      if is_upper(name[it]) {
         if it > 0 && name[it-1] != #char "_" {
            add_underscore = true;
         }

         if it-1 >= 0 && is_upper(name[it-1]) {
            add_underscore = false;
         }
      }

      if add_underscore {
         add_underscore = false;
         append(*b, "_");
      }

      append(*b, to_lower(name[it]));
   }

   return builder_to_string(*b);
}

#import "File";
#import "Basic";
#import "String";
#import "Compiler";
#import "BuildCpp";
#import "Bindings_Generator";
