#include "qjs_sdl_surface.h"
#include "qjs_blob.h"
#include "jsffi.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_gpu.h>
#include <SDL3/SDL_surface.h>
#include <string.h>
#include "qjs_sdl.h"
#include "qjs_common.h"

JSValue pixelformat2js(JSContext *js, SDL_PixelFormat fmt)
{
  const char *str = pixelformat2str(fmt);
  return JS_NewString(js, str);
}

SDL_PixelFormat js2pixelformat(JSContext *js, JSValue v)
{
  if (JS_IsNull(v)) return SDL_PIXELFORMAT_UNKNOWN;
  const char *s = JS_ToCString(js, v);
  if (!s) return SDL_PIXELFORMAT_UNKNOWN;
  
  SDL_PixelFormat fmt = str2pixelformat(s);
  JS_FreeCString(js,s);
  return fmt;
}   

typedef struct { const char *name; SDL_ScaleMode mode; } scale_entry;

static const scale_entry k_scale_table[] = {
  { "nearest", SDL_SCALEMODE_NEAREST },
  { "linear",  SDL_SCALEMODE_LINEAR  },
  { NULL,      SDL_SCALEMODE_LINEAR }  /* fallback */
};

static JSValue scalemode2js(JSContext *js, SDL_ScaleMode mode){
  const scale_entry *it;
  for(it = k_scale_table; it->name; ++it)
    if(it->mode == mode) break;
  return JS_NewString(js, it->name ? it->name : "nearest");
}

SDL_ScaleMode js2SDL_ScaleMode(JSContext *js, JSValue v){
  if(JS_IsNull(v)) return SDL_SCALEMODE_NEAREST;
  const char *s = JS_ToCString(js, v);
  if(!s) return SDL_SCALEMODE_NEAREST;
  const scale_entry *it;
  for(it = k_scale_table; it->name; ++it)
    if(!strcmp(it->name, s)) break;
  JS_FreeCString(js, s);
  return it->mode;
}

// SDL_Surface free function
void SDL_Surface_free(JSRuntime *rt, SDL_Surface *s) {
  if (s->flags & SDL_SURFACE_PREALLOCATED)
    free(s->pixels);
  SDL_DestroySurface(s);
}

// Class definition
QJSCLASS(SDL_Surface,)

// SDL_Surface methods
JSC_CCALL(surface_blit,
  SDL_Surface *dst = js2SDL_Surface(js, self);

  irect dr = {0},  *pdr = NULL;
  if(!JS_IsNull(argv[0])){ dr = js2irect(js, argv[0]); pdr = &dr; }

  SDL_Surface *src = js2SDL_Surface(js, argv[1]);
  if (!src)
    return JS_ThrowReferenceError(js, "Argument must be a surface.");

  irect sr = {0}, *psr = NULL;
  if(!JS_IsNull(argv[2])){ sr = js2irect(js, argv[2]); psr = &sr; }

  SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[3]);
  
  SDL_SetSurfaceBlendMode(src, SDL_BLENDMODE_NONE);
  
  SDL_BlitSurfaceScaled(src, psr, dst, pdr, mode);
)

JSC_CCALL(surface_scale,
  SDL_Surface *src = js2SDL_Surface(js,self);
  HMM_Vec2 wh = js2vec2(js,argv[0]);
  SDL_ScaleMode mode = js2SDL_ScaleMode(js, argv[1]);
  SDL_Surface *new = SDL_ScaleSurface(src, wh.x, wh.y, mode);
  ret = SDL_Surface2js(js,new);
)

static SDL_PixelFormatDetails pdetails = {
    .format = SDL_PIXELFORMAT_RGBA8888, // Standard RGBA8888 format
    .bits_per_pixel = 32,              // 8 bits per channel, 4 channels = 32 bits
    .bytes_per_pixel = 4,              // 4 bytes per pixel
    .padding = {0, 0},                 // Unused padding
    .Rmask = 0xFF000000,               // Red mask
    .Gmask = 0x00FF0000,               // Green mask
    .Bmask = 0x0000FF00,               // Blue mask
    .Amask = 0x000000FF,               // Alpha mask
    .Rbits = 8,                        // 8 bits for Red
    .Gbits = 8,                        // 8 bits for Green
    .Bbits = 8,                        // 8 bits for Blue
    .Abits = 8,                        // 8 bits for Alpha
    .Rshift = 24,                      // Red shift
    .Gshift = 16,                      // Green shift
    .Bshift = 8,                       // Blue shift
    .Ashift = 0                        // Alpha shift
};

JSC_CCALL(surface_fill,
  SDL_Surface *src = js2SDL_Surface(js,self);
  colorf color = js2color(js,argv[0]);
  rect r = {
    .x = 0,
    .y = 0,
    .w = src->w,
    .h = src->h
  };
  SDL_FillSurfaceRect(src, &r, SDL_MapRGBA(&pdetails, NULL, color.r*255,color.g*255,color.b*255,color.a*255));
)

JSC_CCALL(surface_rect,
  SDL_Surface *dst = js2SDL_Surface(js,self);
  rect r = js2rect(js,argv[0]);  
  colorf color = js2color(js,argv[1]);
  SDL_FillSurfaceRect(dst,&r,SDL_MapRGBA(&pdetails,NULL, color.r*255,color.g*255,color.b*255,color.a*255));
) 

JSC_CCALL(surface_convert,
  SDL_Surface *surf = js2SDL_Surface(js,self);
  SDL_PixelFormat fmt = js2pixelformat(js, argv[0]);
  SDL_Surface *dst = SDL_ConvertSurface(surf, fmt);
  if (!dst) return JS_ThrowInternalError(js, "Convert failed: %s", SDL_GetError());
  
  return SDL_Surface2js(js, dst);
)

JSC_CCALL(surface_dup,
  SDL_Surface *surf = js2SDL_Surface(js,self);
  SDL_Surface *conv = SDL_DuplicateSurface(surf);
  if (!conv)
    return JS_ThrowReferenceError(js, "could not blit to dup'd surface: %s", SDL_GetError());

  return SDL_Surface2js(js,conv);
)

JSC_CCALL(surface_pixels,
  SDL_Surface *surf = js2SDL_Surface(js, self);

  int locked = 0;
  if (SDL_MUSTLOCK(surf))
    if (SDL_LockSurface(surf) < 0)
      return JS_ThrowReferenceError(js, "Lock surface failed: %s", SDL_GetError());
  
  size_t byte_size;
  if (SDL_ISPIXELFORMAT_FOURCC(surf->format)) {
    /* Planar/YUV formats: use BitsPerPixel to compute true size   */
    printf("FOURCC!!! Bits is %d\n", SDL_BYTESPERPIXEL(surf->format));
    byte_size = (size_t)surf->pitch * surf->h * SDL_BYTESPERPIXEL(surf->format);
  } else
    byte_size = (size_t)surf->pitch * surf->h;

  ret = js_new_blob_stoned_copy(js, surf->pixels, byte_size);

  if (locked) SDL_UnlockSurface(surf);
)

JSC_CCALL(surface_get_width,
  SDL_Surface *s = js2SDL_Surface(js,self);
  return JS_NewFloat64(js, s->w);
)

JSC_CCALL(surface_get_height,
  SDL_Surface *s = js2SDL_Surface(js,self);
  return JS_NewFloat64(js, s->h);
)

JSC_CCALL(surface_get_format,
  SDL_Surface *s = js2SDL_Surface(js,self);
  return pixelformat2js(js, s->format);
)

JSC_CCALL(surface_get_pitch,
  SDL_Surface *s = js2SDL_Surface(js,self);
  return JS_NewFloat64(js, s->pitch);
)

JSC_CCALL(surface_toJSON,
  SDL_Surface *surf = js2SDL_Surface(js,self);
  
  // Create the result object
  JSValue obj = JS_NewObject(js);
  
  // Add width and height
  JS_SetPropertyStr(js, obj, "width", JS_NewInt32(js, surf->w));
  JS_SetPropertyStr(js, obj, "height", JS_NewInt32(js, surf->h));
  
  // Add format
  JS_SetPropertyStr(js, obj, "format", pixelformat2js(js, surf->format));
  
  // Add pitch
  JS_SetPropertyStr(js, obj, "pitch", JS_NewInt32(js, surf->pitch));
  
  // Lock surface if needed
  int locked = 0;
  if (SDL_MUSTLOCK(surf)) {
    if (SDL_LockSurface(surf) < 0) {
      JS_FreeValue(js, obj);
      return JS_ThrowInternalError(js, "Lock surface failed: %s", SDL_GetError());
    }
    locked = 1;
  }
  
  // Add pixels as ArrayBuffer
  size_t byte_size = surf->pitch * surf->h;
  JSValue pixels = js_new_blob_stoned_copy(js, surf->pixels, byte_size);
  JS_SetPropertyStr(js, obj, "pixels", pixels);
  
  // Unlock if we locked
  if (locked)
    SDL_UnlockSurface(surf);
  
  return obj;
)

// Constructor for SDL_Surface
JSC_CCALL(surface_constructor,
  if (argc < 1)
    return JS_ThrowTypeError(js, "Surface constructor requires an object argument");
  
  // Get width and height
  int width, height;
  JS_GETATOM(js, width, argv[0], width, number)
  JS_GETATOM(js, height, argv[0], height, number)
  
  if (!width || !height)
    return JS_ThrowTypeError(js, "Surface constructor requires width and height properties");
  
  // Check for pixel format
  SDL_PixelFormat format = SDL_PIXELFORMAT_RGBA32;  // default
  JSValue format_val = JS_GetPropertyStr(js, argv[0], "format");
  if (!JS_IsNull(format_val)) {
    format = js2pixelformat(js, format_val);
  }
  JS_FreeValue(js, format_val);
  
  // Check for pixel data
  JSValue pixels_val = JS_GetPropertyStr(js, argv[0], "pixels");
  if (!JS_IsNull(pixels_val)) {
    // Create surface from pixel data
    size_t len;
    void *raw = js_get_blob_data(js, &len, pixels_val);
    
    if (!raw) {
      JS_FreeValue(js, pixels_val);
      return JS_ThrowTypeError(js, "pixels property must be an ArrayBuffer");
    }
    
    // Get pitch if provided, otherwise calculate it
    int pitch;
    JSValue pitch_val = JS_GetPropertyStr(js, argv[0], "pitch");
    if (!JS_IsNull(pitch_val)) {
      pitch = js2number(js, pitch_val);
      JS_FreeValue(js, pitch_val);
    } else {
      // Calculate pitch based on format
      int bytes_per_pixel = SDL_BYTESPERPIXEL(format);
      pitch = width * bytes_per_pixel;
    }
    
    // Copy the pixel data
    void *pixels_copy = malloc(len);
    if (!pixels_copy) {
      JS_FreeValue(js, pixels_val);
      return JS_ThrowOutOfMemory(js);
    }
    memcpy(pixels_copy, raw, len);
    
    SDL_Surface *surface = SDL_CreateSurfaceFrom(width, height, format, pixels_copy, pitch);
    if (!surface) {
      free(pixels_copy);
      JS_FreeValue(js, pixels_val);
      return JS_ThrowInternalError(js, "Failed to create surface from pixels: %s", SDL_GetError());
    }
    
    JS_FreeValue(js, pixels_val);
    ret = SDL_Surface2js(js, surface);
  } else {
    // Create blank surface
    SDL_Surface *surface = SDL_CreateSurface(width, height, format);
    if (!surface)
      return JS_ThrowInternalError(js, "Failed to create surface: %s", SDL_GetError());
      
    ret = SDL_Surface2js(js, surface);
  }
)

static const JSCFunctionListEntry js_SDL_Surface_funcs[] = {
  MIST_FUNC_DEF(surface, blit, 4),
  MIST_FUNC_DEF(surface, scale, 1),
  MIST_FUNC_DEF(surface, fill,1),
  MIST_FUNC_DEF(surface, rect,2),
  MIST_FUNC_DEF(surface, dup, 0),
  MIST_FUNC_DEF(surface, pixels, 0),
  MIST_FUNC_DEF(surface, convert, 1),
  MIST_FUNC_DEF(surface, toJSON, 0),
  JS_CGETSET_DEF("width", js_surface_get_width, NULL),
  JS_CGETSET_DEF("height", js_surface_get_height, NULL),
  JS_CGETSET_DEF("format", js_surface_get_format, NULL),
  JS_CGETSET_DEF("pitch", js_surface_get_pitch, NULL),
};

JSValue js_sdl_surface_use(JSContext *js)
{
  QJSCLASSPREP_FUNCS(SDL_Surface)
  
  // Return a constructor function that creates SDL_Surface objects
  JSValue ctor = JS_NewCFunction2(js, js_surface_constructor, "surface", 1, JS_CFUNC_constructor, 0);
  
  JSValue proto = JS_GetClassProto(js, js_SDL_Surface_id);
  JS_SetConstructor(js, ctor, proto);
  JS_FreeValue(js, proto);
  
  return ctor;
}