#include <janet.h>
#include <GLFW/glfw3.h>
#include <webgpu/webgpu.h>
#include <webgpu/webgpu-release.h>
#include <glfw3webgpu.h>

// A simple structure holding information shared with the
// onAdapterRequestEnded or onDeviceRequestEnded callback.
typedef struct {
  bool requestEnded;
  union{
    WGPUAdapter adapter;
    WGPUDevice device;
  };
} UserData;


// Callback called by wgpuInstanceRequestAdapter when the request returns
// regular C function to become a pointer, which is what
// wgpuInstanceRequestAdapter expects (WebGPU being a C API).
void onAdapterRequestEnded_callback(WGPURequestAdapterStatus status,
                                    WGPUAdapter adapter,
                                    char const * message,
                                    void * pUserData) {
  UserData *userData = (UserData*)(pUserData);

  if (status == WGPURequestAdapterStatus_Success) {
    userData->adapter = adapter;
  }
  else {
    fprintf(stderr, "Could not get WebGPU adapter: ");
    fprintf(stderr, "%s", message);
  }

  userData->requestEnded = true;
}

/**
 * Utility function to get a WebGPU adapter, so that
 *     WGPUAdapter adapter = requestAdapter(options);
 * is roughly equivalent to
 *     const adapter = await navigator.gpu.requestAdapter(options);
 */
WGPUAdapter *requestAdapter_smalloc(WGPUInstance instance,
                                    WGPURequestAdapterOptions const * options) {
  WGPUAdapter *result = NULL;
  UserData userData;
  userData.adapter = NULL;
  userData.requestEnded = false;

  // Call to the WebGPU request adapter procedure
  wgpuInstanceRequestAdapter(
    instance /* equivalent of navigator.gpu */,
    options,
    onAdapterRequestEnded_callback, 
    (void*)&userData
  );

  int64_t i = -1;
  if(!userData.requestEnded){
    int64_t i = 0;
    while(!userData.requestEnded && i < 100000)
    {
      i++;
    }
  }

  if(userData.requestEnded){
    if(i != -1)
      printf("Got adapter after %Id cycles\n", i);
    result = janet_smalloc(sizeof(WGPUAdapter));
    *result = userData.adapter;
  }
  else
  {
    fprintf(stderr, "Timeout: No device after (%Id) cycles\n", i);
  }
  return result;
}

void onDeviceRequestEnded_callback(WGPURequestDeviceStatus status,
                                   WGPUDevice device,
                                   char const * message,
                                   void * pUserData) {
  UserData *userData = (UserData*)(pUserData);

  if (status == WGPURequestDeviceStatus_Success) {
    userData->device = device;
  } else {
    fprintf(stderr, "Could not get WebGPU device: ");
    fprintf(stderr, "%s", message);
  }

  userData->requestEnded = true;
}


/**
 * Utility function to get a WebGPU device, so that
 *     WGPUAdapter device = requestDevice(adapter, options);
 * is roughly equivalent to
 *     const device = await adapter.requestDevice(descriptor);
 * It is very similar to requestAdapter
 */
WGPUDevice *requestDevice_smalloc(WGPUAdapter adapter,
                                  WGPUDeviceDescriptor const * descriptor) {
  WGPUDevice *result = NULL;
  UserData userData;
  userData.device = NULL;
  userData.requestEnded = false;

  wgpuAdapterRequestDevice(
    adapter,
    descriptor,
    onDeviceRequestEnded_callback,
    (void*)&userData
  );
  
  int64_t i = -1;
  if(!userData.requestEnded){
    int64_t i = 0;
    while(!userData.requestEnded && i < 100000)
    {
      i++;
    }
  }

  if(userData.requestEnded){
    if(i != -1)
      printf("Got device after %Id cycles\n", i);
    result = janet_smalloc(sizeof(WGPUDevice));
    *result = userData.device;
  }
  else
  {
    fprintf(stderr, "Timeout: No device after (%Id) cycles\n", i);
  }
  return result;
}
