#include "main.h"

#ifdef __APPLE__ 
#include <Cocoa/Cocoa.h>
#endif

#if INTERFACE
#include <ctype.h>
#include <stdio.h>  
#include <time.h>

#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>

#ifdef WIN32
  #define SOKOL_IMPL
#endif

// LIBS ////////////////////////////////////////////////////////
#include "stb_ds.h"

#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_time.h"
#include "sokol_gl.h"
#include "sokol_log.h"
#include "fontstash.h"
#include "sokol_fontstash.h"

#include "cmixer.h"
#include "minimp3.h"

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h" 

#include "libtess.h"

#ifndef DISTRIBUTION_BUILD
#include "triangle.h"
#include "xatlas_c.h"
#endif

// CORE ////////////////////////////////////////////////////////
#include "gfx.h"
#include "vector.h"

// ASSETS ////////////////////////////////////////////////////////
#include "resource.h"

#include "defaultShader.glsl.h"
#include "forwardRender.glsl.h"
// #include "forwardStatic.glsl.h"
  // "shadowStatic"
  // "shadowPreview"
  // "deferredStatic"
  // "deferredDynamic"
  // "deferredLight"
  // "deferredCombine"
  // "voxelRender"
  // "voxelAlpha"
  // "voxelAverage"
  // "voxelFlip"
  // "voxelTrace"
  // "voxelInject"
  // "voxelLight"
  // "voxelCombine"
  // "voxelOcclusion"
  // "voxelBasemip"
  // "voxelMipmap"
  // "voxelLightmip"
  // "voxelNormalmip"
  // "probeLights"
  // "postProcess"
  // "pathTrace"

typedef union int2 {
  struct {int x, y; };
  struct {int width, height; };
} int2;

typedef struct { unsigned int width; unsigned int height; } size;

typedef struct Rectangle {
    float x;
    float y;
    float width;
    float height;
} Rectangle;

typedef struct { 
  bool enabled;
} frameTween;

typedef struct { 
  bool blank;       
  bool isKey;       
  bool released;
  u16 index;
  u16 length; 
  u16 ox;
  u16 oy;            
  u16 strokes;
  u16 width;           
  u16 height;           
  u32 lastDrawn;
  u32 lastSave;
  u32 id;          
  frameTween tween;       
} frame_t;


typedef struct Window {
  struct  {
    Shader defaultShader;
    Shader forwardRender;
  } shaders;

  char * title;
  float dpi_scale;
  int thread;
  int monitor;
  int set_monitor;
  double frame_time;

  bool close;
  bool resized;
  bool fullscreen;                    
  bool vsync;
  bool set_size;
  bool set_pos;

  int2 position;        
  int2 new_pos;
  int2 size;

  struct {
    int x,y;
    int dx,dy;
    int px,py;
    int pressed,released;
  } mouse;

  size display;                       
  size screen;                        
  size fbo;                    
  size render;                          
  size new_size;

} Window;

typedef bool ma_engine;
typedef struct Thread {
  Window window;
  float rx, ry;
  
  sg_backend backend; 

  FONScontext* fons;
  lua_State* lState;
  ma_engine audio;

  struct {
    int inconsolata;
    int interfont;
  } fonts;

} Thread;

#endif


/////////////////////////////////////////////////////////////
// #define REAL float
#ifdef SOKOL_METAL
  #import <Metal/Metal.h>
  #import <MetalKit/MTKView.h>
  #import <QuartzCore/CAMetalLayer.h>
#endif

static Thread state;
static int uargc;
static char** uargv;
double previousTime = 0;
double tick = 0;
bool pass = false;
double globalTime = 0;
static images_t images;
/////////////////////////////////////////////////////////////

Window* getWindow(int id){
  return &(state.window);
}

images_t getInterfaceImages(){
  return images;
}

bool lineSegmentIntersection(
double Ax, double Ay,
double Bx, double By,
double Cx, double Cy,
double Dx, double Dy,
double *X, double *Y) {

  double  distAB, theCos, theSin, newX, ABpos ;

  //  Fail if either line segment is zero-length.
  if (Ax==Bx && Ay==By || Cx==Dx && Cy==Dy) return false;

  //  Fail if the segments share an end-point.
  if (Ax==Cx && Ay==Cy || Bx==Cx && By==Cy
  ||  Ax==Dx && Ay==Dy || Bx==Dx && By==Dy) {
    return false; }

  //  (1) Translate the system so that point A is on the origin.
  Bx-=Ax; By-=Ay;
  Cx-=Ax; Cy-=Ay;
  Dx-=Ax; Dy-=Ay;

  //  Discover the length of segment A-B.
  distAB=sqrt(Bx*Bx+By*By);

  //  (2) Rotate the system so that point B is on the positive X axis.
  theCos=Bx/distAB;
  theSin=By/distAB;
  newX=Cx*theCos+Cy*theSin;
  Cy  =Cy*theCos-Cx*theSin; Cx=newX;
  newX=Dx*theCos+Dy*theSin;
  Dy  =Dy*theCos-Dx*theSin; Dx=newX;

  //  Fail if segment C-D doesn't cross line A-B.
  if (Cy<0. && Dy<0. || Cy>=0. && Dy>=0.) return false;

  //  (3) Discover the position of the intersection point along line A-B.
  ABpos=Dx+(Cx-Dx)*Dy/(Dy-Cy);

  //  Fail if segment C-D crosses line A-B outside of segment A-B.
  if (ABpos<0. || ABpos>distAB) return false;

  //  (4) Apply the discovered position to line A-B in the original coordinate system.
  *X=Ax+ABpos*theCos;
  *Y=Ay+ABpos*theSin;

  //  Success.
  return true; 
}
  
/////////////////////////////////////////////////////////////


bool TextIsEqual(const char *text1, const char *text2) {
    bool result = false;

    if (strcmp(text1, text2) == 0) result = true;

    return result;
}
const char *GetFileExtension(const char *fileName) {
    const char *dot = strrchr(fileName, '.');

    if (!dot || dot == fileName) return NULL;

    return dot;
}

const char *TextToLower(const char *text) {
    static char buffer[1024] = { 0 };
    for (int i = 0; i < 1024; i++) {
        if (text[i] != '\0') {
            buffer[i] = (char)tolower(text[i]);
            //if ((text[i] >= 'A') && (text[i] <= 'Z')) buffer[i] = text[i] + 32;
        }
        else { buffer[i] = '\0'; break; }
    }
    return buffer;
}


unsigned char *loadFileData(const char *fileName, unsigned int *bytesRead,bool internal) {

    unsigned char *data = NULL;
    *bytesRead = 0;

    if (fileName != NULL) {
        
        if (internal) {
            // data = package_loadFileData(fileName, bytesRead);
            // return data;
        }

        FILE *file = fopen(fileName, "rb");

        if (file != NULL) {
            // WARNING: On binary streams SEEK_END could not be found,
            // using fseek() and ftell() could not work in some (rare) cases
            fseek(file, 0, SEEK_END);
            int size = ftell(file);
            fseek(file, 0, SEEK_SET);

            if (size > 0) {
                data = (unsigned char *)malloc(size*sizeof(unsigned char));

                // NOTE: fread() returns number of read elements instead of bytes, so we read [1 byte, size elements]
                unsigned int count = (unsigned int)fread(data, sizeof(unsigned char), size, file);
                *bytesRead = count;

                if (count != size) printf( "FILEIO: [%s] File partially loaded\n", fileName);
                else printf( "FILEIO: [%s] File loaded successfully\n", fileName);
            } else{ printf( "FILEIO: [%s] Failed to read file\n", fileName);}
            fclose(file);
        } else{ printf( "FILEIO: [%s] Failed to open file\n", fileName);}
    } else{ printf( "FILEIO: File name provided is not valid\n");}

    return data;
}



Image loadImageData(const char *fileName,bool internal){
  Image image;
  unsigned int fileSize = 0;
  unsigned char *fileData = loadFileData(fileName, &fileSize,internal);

  if (fileData != NULL) {
    char fileExtLower[16] = { 0 };
    strcpy(fileExtLower, TextToLower(GetFileExtension(fileName)));
    if (TextIsEqual(fileExtLower, ".png") || TextIsEqual(fileExtLower, ".tga")){
      image = imgFromMem(fileData, fileSize);
    }
    if (!internal)
      free(fileData);
  }

  return image;
}




Texture texFromFile(const char* file,bool internal){
  Image img = loadImageData(file,internal);
  Texture2D tex = texFromData(img);
  free(img.data);
  return tex;
}

static image_hash_t* image_hash;

Image getInterfaceImage(char* name){
  Image image = {0};
  printf("ptr_hash count: %i, %s, %i\n",shlen(image_hash),name,shgets(image_hash,name).len);
  imgFromMem(shgets(image_hash,name).ptr,shgets(image_hash,name).len);
  return image;
}

/////////////////////////////////////////////////////////////////

// int loadluabuff(const char *buff, size_t sz, const char *name){
//   int error = luaL_loadbuffer (  state.lState ,buff,sz,name) ||
//           lua_pcall(state.lState, 0, 0, 0);
//   if (error) {
//     fprintf(stderr, "%s", lua_tostring(state.lState, -1));
//     lua_pop(state.lState, 1);  /* pop error message from the stack */
//   }
//   return error;
// }
static int round_pow2(float v) {
    uint32_t vi = ((uint32_t) v) - 1;
    for (uint32_t i = 0; i < 5; i++) {
        vi |= (vi >> (1<<i));
    }
    return (int) (vi + 1);
}
void* unused1 = &sg_make_image;
void* unused2 = &sg_make_pipeline;

void setFontFace(int thread, int face){
  if (face==1) 
    fonsSetFont(state.fons, state.fonts.inconsolata);
  else if(face==2)
    fonsSetFont(state.fons, state.fonts.interfont);
}
void setFontSize(int thread,int size){
  fonsSetSize(state.fons, size*state.window.dpi_scale);
}
void setFontColor(int thread,int r,int g,int b,int a){

}
void drawText(int thread,float x, float y, const char* string,int align,double color[5]){

  uint32_t hex = sfons_rgba(
    (uint8_t)fmin(255,color[1]*255),
    (uint8_t)fmin(255,color[2]*255),
    (uint8_t)fmin(255,color[3]*255),
    (uint8_t)fmin(255,color[4]*255));
  fonsSetColor(state.fons, hex);
  if (align==0) 
    fonsSetAlign(state.fons, FONS_ALIGN_LEFT | FONS_ALIGN_TOP);
  else if(align==1)
    fonsSetAlign(state.fons, FONS_ALIGN_CENTER | FONS_ALIGN_TOP);
  // clock_t t; 
  // t = clock(); 
  fonsDrawText(state.fons, 
    x*state.window.dpi_scale,
    y*state.window.dpi_scale, 
    string,NULL
  );
  // t = clock() - t; 
  // double timed = ((double)t)/CLOCKS_PER_SEC;
  // printf("timed %f\n", timed);
}

int getCurrentMonitor(Window* window) {
}

int getMaxTextureSize(){ 
  return sg_query_limits().max_image_size_2d;
}
void toggleFullscreen(Window* window){
  sapp_toggle_fullscreen();
}


static void error_callback(int error, const char* description)
{
    fprintf(stderr, "Error: %s\n", description);
}

void reset_sgl(Window* window){
  sgl_defaults();
  sgl_matrix_mode_projection();
  // sgl_ortho(0.0f, window->size.width*window->dpi_scale, window->size.height*window->dpi_scale,0.0,-1.0f, +1.0f);
  sgl_ortho(0.0f, sapp_widthf(), sapp_heightf(), 0.0f, -1.0f, +1.0f);
}
void drawFons(int thread){
  sfons_flush(state.fons);
  // sgl_draw();
}
// void beginWindowPass(int w,int h){
//   sg_pass_action pass_action = {
//     .colors[0] = { .load_action = SG_LOADACTION_CLEAR, .clear_value = { 0.25f, 0.5f, 0.5f, 1.0f } }
//   };
//   sg_begin_pass(&pass_action,w,h);
// }
// void beginCanvasPass(){
//   sg_pass_action pass_action = {
//     .colors[0] = { .load_action = SG_LOADACTION_CLEAR, .value = { 0.25f, 0.5f, 0.5f, 1.0f } }
//   };
//   sg_begin_default_pass(pass_action,w,h);
// }
// void endPass(){
//   sg_end_pass();
// }

sg_swapchain getSwapchain(){
  return sglue_swapchain();
}

static void frame(void){
  state.window.dpi_scale = sapp_dpi_scale();
  #ifdef SOKOL_METAL
    @autoreleasepool {
      // CGSize new_size = {
      //   .width = state.window.size.width*state.window.dpi_scale, 
      //   .height=state.window.size.height*state.window.dpi_scale,
      // };
      // layer.drawableSize = new_size;
      #endif

  double currentTime = stm_sec(stm_now());
  state.window.frame_time = currentTime-previousTime;
  previousTime = currentTime;
  globalTime += state.window.frame_time;

  state.window.size.width= (int)sapp_widthf()/(sapp_dpi_scale());
  state.window.size.height = (int)sapp_heightf()/(sapp_dpi_scale());
  
  char FT[100];
    snprintf ( FT, 100, "vsync:%s %0.4f", state.window.vsync?"ON":"OFF",state.window.frame_time );

  // printf("frame: %0.6f\n",state.window.frame_time);

    fonsClearState(state.fons);
    uint32_t white = sfons_rgba(255, 255, 255, 255);
    fonsSetFont(state.fons, state.fonts.interfont);
    fonsSetSize(state.fons, 20.0f);
    fonsSetColor(state.fons, white);

    clock_t t; 
    t = clock(); 
    if (state.lState!=NULL) {
      lua_getglobal(state.lState, "engine_frame");
      if (lua_pcall(state.lState, 0, 0, 0) != 0){
        luaL_error(state.lState, "error running function `f': %s",lua_tostring(state.lState, -1));
      }
    }
    t = clock() - t; 
    double timed = ((double)t)/CLOCKS_PER_SEC;
    tick += stm_sec(stm_now())-currentTime;

    sg_commit();
    // printf("tick: %0.6f\n",glfwGetTime()-currentTime);
    #ifdef SOKOL_METAL
    }
    #endif
}
void setWindowSize(int w, int h){
 sapp_set_window_size(w,  h);
}

void setVsync(bool sync){
  state.window.vsync = sync;
  #ifdef __APPLE__
  #ifdef SOKOL_GLCORE33
    NSWindow* nswindow = sapp_macos_get_window();
    GLint swapInt = sync?1:0;
    NSOpenGLContext* ctx = [nswindow.contentView openGLContext];
    [ctx setValues:&swapInt forParameter:NSOpenGLContextParameterSwapInterval];
    [ctx makeCurrentContext];
  #elif SOKOL_METAL
    NSWindow* nswindow = sapp_macos_get_window();
    CAMetalLayer* layer = nswindow.contentView.layer;
    layer.displaySyncEnabled = sync;
  #endif
  #endif
}

void* stdAlloc(void* userData, unsigned int size)
{
  int* allocated = ( int*)userData;
  TESS_NOTUSED(userData);
  *allocated += (int)size;
  return malloc(size);
}

void stdFree(void* userData, void* ptr)
{
  TESS_NOTUSED(userData);
  free(ptr);
}

static void init(void) {
  TESSalloc ma;
  int allocated = 0;
  memset(&ma, 0, sizeof(ma));
  ma.memalloc = stdAlloc;
  ma.memfree = stdFree;
  ma.userData = (void*)&allocated;
  ma.extraVertices = 256; // realloc not provided, allow 256 extra vertices.
  TESStesselator* tess = 0;
  frandom(50.0f);
  tess = tessNewTess(&ma);
  xatlasAtlas* zatlas = xatlasCreate();

  state.window.thread = 0;
  include_images(&images);
  stm_setup();
  lua_State *L = luaL_newstate();
  state.lState = L;
  luaL_openlibs(L);

  if (L == NULL) {
    puts("Unable to initialize Lua");
  }else{
    lua_newtable(L);

    int i = 0;
    while (uargc != i) {
      lua_pushstring(L, uargv[i]);
      lua_rawseti(L, -2, i);
      i++;
    }

    lua_setglobal(L, "arg");

    puts("Lua initialized");
    if (uargv[1]!=NULL && strcmp(uargv[1], "live") == 0){
      printf("%s\n", "load live");
      printf("argv[%s]\n",uargv[1]);
      // int status = luaL_loadfile(L, "src/main.lua");
      // int ret = lua_pcall(L, 0, 0, 0);
      // if (ret != 0) {
      //   luaL_error(L,"error runnign script: %s", lua_tostring(L,-1));
      //   fprintf(stderr, "%s\n", lua_tostring(L, -1)); 
      // }
      if (luaL_loadbuffer(L, main_lua, main_lua_len, "main.lua")==0){
        if (lua_pcall(L, 0, 0, 0) != 0) {
          luaL_error(L,"error runnign script: %s", lua_tostring(L,-1));
          fprintf(stderr, "%s\n", lua_tostring(L, -1)); 
          // fputs(luaL_checkstring(L, -1), stderr);
          // fputc('\n', stderr);
        }
      }
    }else{
      printf("%s\n", "load internal");
      unsigned char source[source_lua_len + 1];
      memcpy(source, source_lua, source_lua_len);
      source[source_lua_len] = '\0'; 

      lua_pushlstring(L, source,source_lua_len);
      lua_setglobal(L, "source_lua");
      if (luaL_loadbuffer(L, main_lua, main_lua_len, "main.lua")==0){
        if (lua_pcall(L, 0, 0, 0) != 0) {
          luaL_error(L,"error runnign script: %s", lua_tostring(L,-1));
          fprintf(stderr, "%s\n", lua_tostring(L, -1)); 
          // fputs(luaL_checkstring(L, -1), stderr);
          // fputc('\n', stderr);
        }
      }
    }

  }


  sg_setup(&(sg_desc){
    .buffer_pool_size = 512,
    .environment = sglue_environment(),
    .logger.func = slog_func,
  });


  sg_desc desc = sg_query_desc();

  state.window.mouse.px = 0;
  state.window.mouse.py = 0;
  state.window.set_monitor = -1;

  state.backend = sg_query_backend();
  state.window.dpi_scale = sapp_dpi_scale();

  {
    sg_shader_desc desc;
    memcpy(&desc,defaultShader_shader_desc(state.backend),sizeof(sg_shader_desc));

    state.window.shaders.defaultShader = (Shader) {
      .desc = desc,
      .program = sg_make_shader(&desc),
      .name = "defaultShader",
      .vs_uniforms= {
        .ptr = (defaultShader_vs_params_t*)malloc(sizeof(defaultShader_vs_params_t)),
        .size = sizeof(defaultShader_vs_params_t)
      },
      .fs_uniforms= {
        .ptr = (defaultShader_fs_params_t*)malloc(sizeof(defaultShader_fs_params_t)),
        .size = sizeof(defaultShader_fs_params_t)
      },
    };
  }

  {
    sg_shader_desc desc;
    memcpy(&desc,forwardRender_main_shader_desc(state.backend),sizeof(sg_shader_desc));

    state.window.shaders.forwardRender = (Shader) {
      .desc = desc,
      .program = sg_make_shader(&desc),
      .name = "forwardRender",
      .vs_uniforms= {
        .ptr = (forwardRender_vs_params_t*)malloc(sizeof(forwardRender_vs_params_t)),
        .size = sizeof(forwardRender_vs_params_t)
      },
      .fs_uniforms= {
        .ptr = (forwardRender_fs_params_t*)malloc(sizeof(forwardRender_fs_params_t)),
        .size = sizeof(forwardRender_fs_params_t)
      },
    };
  }

  printf("context created\n");

  sgl_setup(&(sgl_desc_t){0});

  const int atlas_dim = round_pow2(512.0f);
  FONScontext* fons_context =  sfons_create(&(sfons_desc_t){
    .width=atlas_dim, 
    .height=atlas_dim
  });
  state.fons = fons_context;
  state.fonts.inconsolata = FONS_INVALID;
  state.fonts.inconsolata = fonsAddFontMem(state.fons, "sans", fonts_inconsolata, fonts_inconsolata_len,  false);

  state.fonts.interfont = FONS_INVALID;
  state.fonts.interfont = fonsAddFontMem(state.fons, "sans", fonts_interfont, fonts_interfont_len,  false);

  if (state.fonts.inconsolata==FONS_INVALID){
    printf("invalid font%i\n", state.fonts.inconsolata);
  }
  if (state.fonts.interfont==FONS_INVALID){
    printf("invalid font%i\n", state.fonts.interfont);
  }  

  lua_getglobal(state.lState, "engine_run");
  lua_pushnumber(state.lState, state.window.thread);  
  if (lua_pcall(state.lState, 1, 0, 0) != 0){
    luaL_error(state.lState, "error running function `f': %s",lua_tostring(state.lState, -1));
  }


  state.window.vsync = true;
}

void cleanup(void){
  
  sg_shutdown();
  // sargs_shutdown();
}

static void input(const sapp_event* ev) {
  if (ev->type== SAPP_EVENTTYPE_RESIZED){
    state.window.resized=true;
  }

  if (ev->type==SAPP_EVENTTYPE_QUIT_REQUESTED){
    lua_getglobal(state.lState, "engine_quit");
    lua_pcall(state.lState, 0, 0, 0);
    printf("imgay\n");
  }

  if ((ev->type== SAPP_EVENTTYPE_FOCUSED) || (ev->type== SAPP_EVENTTYPE_UNFOCUSED)){
    lua_getglobal(state.lState, "input_focus_callback");
    lua_pushnumber(state.lState, (ev->type== SAPP_EVENTTYPE_FOCUSED)?1:2);
    if (lua_pcall(state.lState, 1, 0, 0) != 0){
      luaL_error(state.lState, "callback error': %s",lua_tostring(state.lState, -1));
      printf( "%s\n", lua_tostring(state.lState, -1)); 
    }
  }

  if ((ev->type== SAPP_EVENTTYPE_MOUSE_SCROLL)){
    lua_getglobal(state.lState, "input_scroll_callback");
    lua_pushnumber(state.lState, ev->scroll_x);
    lua_pushnumber(state.lState, ev->scroll_y);
    if (lua_pcall(state.lState, 2, 0, 0) != 0){
      luaL_error(state.lState, "callback error': %s",lua_tostring(state.lState, -1));
      printf( "%s\n", lua_tostring(state.lState, -1)); 
    }
  }

  if ((ev->type== SAPP_EVENTTYPE_KEY_DOWN) || (ev->type== SAPP_EVENTTYPE_KEY_UP)){
    lua_getglobal(state.lState, "input_key_callback");
    lua_pushnumber(state.lState, ev->key_code);
    lua_pushnumber(state.lState, 0);
    lua_pushnumber(state.lState, (ev->type== SAPP_EVENTTYPE_KEY_DOWN)?1:2);
    if (lua_pcall(state.lState, 3, 0, 0) != 0){
      luaL_error(state.lState, "callback error': %s",lua_tostring(state.lState, -1));
      printf( "%s\n", lua_tostring(state.lState, -1)); 
    }
  }

  if ((ev->type== SAPP_EVENTTYPE_MOUSE_DOWN) || (ev->type== SAPP_EVENTTYPE_MOUSE_UP)){
    lua_getglobal(state.lState, "input_click_callback");
    lua_pushnumber(state.lState, ev->mouse_button+1);
    lua_pushnumber(state.lState, ((ev->type== SAPP_EVENTTYPE_MOUSE_DOWN)?1:2));
    if (lua_pcall(state.lState, 2, 0, 0) != 0){
      luaL_error(state.lState, "callback error': %s",lua_tostring(state.lState, -1));
      printf( "%s\n", lua_tostring(state.lState, -1)); 
    }
  }    

  if (ev->type== SAPP_EVENTTYPE_MOUSE_MOVE){
    lua_getglobal(state.lState, "input_mouse_callback");
    lua_pushnumber(state.lState, ev->mouse_x/state.window.dpi_scale);
    lua_pushnumber(state.lState, ev->mouse_y/state.window.dpi_scale);
    lua_pushnumber(state.lState, ev->mouse_dx/state.window.dpi_scale);
    lua_pushnumber(state.lState, ev->mouse_dy/state.window.dpi_scale);
    if (lua_pcall(state.lState, 4, 0, 0) != 0){
      luaL_error(state.lState, "callback error': %s",lua_tostring(state.lState, -1));
      printf( "%s\n", lua_tostring(state.lState, -1)); 
    }
  }  

}


sapp_desc sokol_main(int argc, char** argv) {
  uargc = argc;
  uargv = argv;
    return (sapp_desc){
        .init_cb = init,
        .frame_cb = frame,
        .event_cb = input,
        .cleanup_cb = cleanup,
        .width = 800,
        .height = 600,
        .high_dpi=true,
        .swap_interval=1,
        // .sample_count = 1,
        .window_title = "Umoupen",
        .icon.sokol_default = true,
    };
}
/////////////////////////////////////////////////////////////////
/*
static linking solution
First, create a struct with function pointers:

  struct myfuncs {
    int (*some_function_name)(int a, int b);
    void *(*another_function_name)(char *x, const char *y);
    ...
  };

Then make a static struct on the C side and fill it with the
function pointers. Make a lightuserdata object from the struct
pointer with lua_pushlightuserdata(). Pass that Lua object to
LuaJIT, e.g. as an argument of lua_call/lua_pcall or via a global

On the LuaJIT side, declare the identical struct with ffi.cdef.
Then cast that lightuserdata argument back to the struct pointer:

  -- Assumes the lightuserdata is in the variable ud.
  myfuncs = ffi.cast("struct myfuncs *", ud)

All of the above should only be done once at initialization.

Now, whenever you want to call one of these functions via the FFI,
simply reference them from the struct. E.g.:

  -- That's really an FFI call.
  local y = myfuncs.some_function_name(1, 2)
  */

/////////////////////////////////////////////////////////////////