/*
 * call-seq:
 *    Win32::API.new(function, prototype='V', return='L', dll='kernel32')
 * 
 * Creates and returns a new Win32::API object. The +function+ is the name
 * of the Windows function.
 * 
 * The +prototype+ is the function prototype for +function+. This can be a
 * string or an array of characters.  The possible valid characters are 'I'
 * (integer), 'L' (long), 'V' (void), 'P' (pointer), or 'K' (callback).
 * The default is void ('V').
 * 
 * The +return+ argument is the return type for the function.  The valid
 * characters are the same as for the +prototype+.  The default is 'L' (long).
 * 
 * The +dll+ is the name of the DLL file that the function is exported from.
 * The default is 'kernel32'.
 * 
 * If the function cannot be found then an API::Error is raised (a subclass
 * of RuntimeError).
 */
static VALUE api_init(int argc, VALUE* argv, VALUE self)
{
   HMODULE hLibrary;
   FARPROC fProc;
   Win32API* ptr;
   int i;
   VALUE v_proc, v_proto, v_return, v_dll, v_bool, v_name;
   
   rb_scan_args(argc, argv, "13", &v_proc, &v_proto, &v_return, &v_dll);
   
   Data_Get_Struct(self, Win32API, ptr);

   /* Convert a string prototype to an array of characters */
   if(rb_respond_to(v_proto, rb_intern("split")))
      v_proto = rb_str_split(v_proto, "");

   /* Set an arbitrary limit of 16 parameters */
   if(16 < RARRAY(v_proto)->len)
      rb_raise(rb_eArgError, "too many parameters: %d\n", RARRAY(v_proto)->len);

   /* Convert a nil or empty prototype to 'V' (void) automatically */
   if(NIL_P(v_proto) || RARRAY(v_proto)->len == 0){
      v_proto = rb_ary_new();
      rb_ary_push(v_proto, rb_str_new2("V"));
   }
   
   /* Set the default dll to 'kernel32' */
   if(NIL_P(v_dll))
        v_dll = rb_str_new2("kernel32");
        
   /* Set the default return type to 'L' (DWORD) */
   if(NIL_P(v_return))
        v_return = rb_str_new2("L");

   SafeStringValue(v_dll);
   SafeStringValue(v_proc);

   hLibrary = LoadLibrary(TEXT(RSTRING(v_dll)->ptr));

   /* The most likely cause of failure is a bad DLL load path */
   if(!hLibrary){
      rb_raise(cAPIError, "LoadLibrary() function failed for '%s': %s",
         RSTRING(v_dll)->ptr,
         StringError(GetLastError())
      );
   }

   ptr->library = hLibrary;

   /* Attempt to get the function.  If it fails, try again with an 'A'
    * appended.  If that fails, try again with a 'W' appended.  If that
    * still fails, raise an API::Error.
    */
   fProc = GetProcAddress(hLibrary, TEXT(RSTRING(v_proc)->ptr));

   if(!fProc){
      VALUE v_ascii = rb_str_new3(v_proc);
      v_ascii = rb_str_cat(v_ascii, "A", 1);
      fProc = GetProcAddress(hLibrary, TEXT(RSTRING(v_ascii)->ptr));

      if(!fProc){
         VALUE v_unicode = rb_str_new3(v_proc);
         v_unicode = rb_str_cat(v_unicode, "W", 1);
         fProc = GetProcAddress(hLibrary, TEXT(RSTRING(v_unicode)->ptr));

         if(!fProc){
            rb_raise(
               cAPIError,
               "GetProcAddress() failed for '%s', '%s' and '%s': %s",
               RSTRING(v_proc)->ptr,
               RSTRING(v_ascii)->ptr,
               RSTRING(v_unicode)->ptr,
               StringError(GetLastError())
            );
         }
      }
   }

   ptr->function = fProc;

   /* Push the numeric prototypes onto our int array for later use. */
   for(i = 0; i < RARRAY(v_proto)->len; i++){
      SafeStringValue(RARRAY(v_proto)->ptr[i]);
      switch(*(char*)StringValuePtr(RARRAY(v_proto)->ptr[i])){
         case 'L':
            ptr->prototype[i] = _T_LONG;
            break;
         case 'P':
            ptr->prototype[i] = _T_POINTER;
            break;
         case 'I': case 'B':
            ptr->prototype[i] = _T_INTEGER;
            break;
         case 'V':
            ptr->prototype[i] = _T_VOID;
            break;
         case 'K':
            ptr->prototype[i] = _T_CALLBACK;
            break;
         default:
            rb_raise(cAPIError, "Illegal prototype '%s'", RARRAY(v_proto)->ptr[i]);
      }
   }

   /* Store the return type for later use.  Automatically convert empty
    * strings or nil to type void.
    */
   if(NIL_P(v_return) || RSTRING(v_return)->len == 0){
      v_return = rb_str_new2("V");
      ptr->return_type = _T_VOID;
   }
   else{
      SafeStringValue(v_return);
      switch(*RSTRING(v_return)->ptr){
         case 'L':
            ptr->return_type = _T_LONG;
            break;
         case 'P':
            ptr->return_type = _T_POINTER;
            break;
         case 'I': case 'B':
            ptr->return_type = _T_INTEGER;
            break;
         case 'V':
            ptr->return_type = _T_VOID;
            break;
         default:
            rb_raise(cAPIError, "Illegal prototype '%s'", RARRAY(v_proto)->ptr[i]);
      }
   }

   rb_iv_set(self, "@dll_name", v_dll);
   rb_iv_set(self, "@function_name", v_proc);
   rb_iv_set(self, "@prototype", v_proto);
   rb_iv_set(self, "@return_type", v_return);

   return self;
}