#module_parameters(STATIC := true);

c_context: #Context;

#scope_export;

using #import,file "../module.jai"(STATIC = STATIC);

expose :: (L: *State, proc: $T, $caller_code := #caller_code, $loc := #caller_location)
#modify {
   return T.(*Type_Info).type == .PROCEDURE, "expose must take a procedure";
} #expand {
   #insert -> string {
      info := T.(*Type_Info_Procedure);
      code := compiler_get_nodes(caller_code);
      call := code.(*Code_Procedure_Call);

      proc := call.arguments_sorted[1].(*Code_Ident);
      basic.assert(proc.kind == .IDENT, "must be an identifier", loc = loc);

      body_builder: basic.String_Builder;
      args_builder: basic.String_Builder;
      call_builder: basic.String_Builder;
      rets_builder: basic.String_Builder;

      if info.return_types.count != 0 {
         basic.append(*call_builder, "\t\t\t\t");

         for info.return_types {
            basic.print_to_builder(*call_builder, "r%", it_index);
            if it_index < info.return_types.count -1 {
               basic.append(*call_builder, ", ");
            }
         }

         basic.append(*call_builder, " := ");
      }

      basic.append(*call_builder, "proc(");

      for < info.argument_types {
         index := -(it_index + 1);
         basic.print_to_builder(*args_builder, "\t\t\ta% := %;\n", it_index, gen_stack_pull(it, index));

         basic.print_to_builder(*call_builder, "a%", it_index);
         if it_index > 0 {
            basic.append(*call_builder, ", ");
         }
      }

      basic.append(*call_builder, ");");

      for info.return_types {
         basic.print_to_builder(*rets_builder, "\t\t\t\t%;", gen_stack_push(it, it_index));
         if it_index < info.return_types.count - 1 {
            basic.append(*rets_builder, "\n");
         }
      }

      return basic.sprint(#string END
      lua_name    :: "%1";
      lua_wrapper :: (L: *State) -> s32 #c_call {
%2
         push_context c_context {
%3
%4
         }

         return %5;
      }
      END,
         proc.name,
         basic.builder_to_string(*args_builder),
         basic.builder_to_string(*call_builder),
         basic.builder_to_string(*rets_builder),
         info.return_types.count,
      );
   }

   info := T.(*Type_Info_Procedure);
   pushcclosure(L, lua_wrapper, info.argument_types.count.(s32));
   setfield(L, LUA_GLOBALSINDEX, lua_name);
}


#scope_file;

gen_stack_pull :: (info: *Type_Info, index: int) -> string {
   if info.type == {
      case .BOOL;
         return basic.tprint("get_lua_bool(L, %)", index);
      case .INTEGER;
         return basic.tprint("tointeger(L, %)", index);
      case .FLOAT;
         return basic.tprint("tonumber(L, %)", index);
      case .STRING;
         return basic.tprint("get_lua_string(L, %)", index);
      case .STRUCT;
         return basic.tprint("get_lua_table(L, %, %)", info.(*Type_Info_Struct).name, index);

      case;
         basic.assert(false, "% (%)", info.type, index);
   }

   return "";
}

gen_stack_push :: (info: *Type_Info, index: int) -> string {
   if info.type == {
      case .BOOL;
         return basic.tprint("pushboolean(L, r%.(s32))", index);
      case .INTEGER;
         return basic.tprint("pushinteger(L, r%)", index);
      case;
         basic.assert(false, "% (%)", info.type, index);
   }

   return "";
}

get_lua_string :: inline (L: *State, index: s32) -> string #c_call {
   len: u64;
   ptr := tolstring(L, index, *len);
   return string.{ data = ptr, count = xx len };
}

get_lua_bool :: inline (L: *State, index: s32) -> bool #c_call {
   return toboolean(L, index) == 1;
}

get_lua_table :: (L: *State, $T: Type, index: s32) -> T #c_call {
   push_context c_context {
      return get_lua_table(L, T.(*Type_Info_Struct), index).(*T).*;
   }
}

temp_storage: [4096]u8;
temp_offset:  int;

get_lua_table :: (L: *State, info: *Type_Info_Struct, index: s32) -> *void {
   res := temp_storage.data + temp_offset;
   memset(res, 0, info.runtime_size);

   temp_offset = (temp_offset + info.runtime_size) % temp_storage.count;

   for info.members {
      defer settop(L, -2);
      pushlstring(L, it.name.data, xx it.name.count);
      rawget(L, -2);

      vp: *void;
      if it.type.type == {
         case .BOOL;
            v := get_lua_bool(L, -1);
            vp = *v;
         case .INTEGER;
            v := tointeger(L, -1);
            if it.type.runtime_size == {
               case 1; vp = *(v.(u8, no_check));
               case 2; vp = *(v.(u16, no_check));
               case 4; vp = *(v.(u32, no_check));
               case 8; vp = *(v.(u64, no_check));
            }
         case .FLOAT;
            v := tonumber(L, -1);
            if it.type.runtime_size == {
               case 4; vp = *(v.(float32, no_check));
               case 8; vp = *(v.(float64, no_check));
            }
         case .STRING;
            v := get_lua_string(L, -1);
            vp = *v;
         case .STRUCT;
            v := get_lua_table(L, it.type.(*Type_Info_Struct), -1);
            vp = *v;

         case;
            basic.assert(false, "% (%)", info.type, index);
         }

      memcpy(res + it.offset_in_bytes, vp, it.type.runtime_size);
   }

   return res;
}


basic :: #import "Basic"; // @future

#import "Compiler";
