#include "qjs_os.h"
#include "qjs_blob.h"
#include "jsffi.h"
#include "qjs_common.h"
#include "qjs_wota.h"
#include "qjs_transform.h"
#include "transform.h"

#include <SDL3/SDL.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <limits.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#include <sys/utsname.h>
#ifdef __linux__
#include <sys/sysinfo.h>
#include <sys/resource.h>
#endif
#endif

JSC_CCALL(os_now, return number2js(js, (double)SDL_GetTicksNS()/1000000000.0))

JSC_CCALL(os_sleep,
  double secs = js2number(js,argv[0]);
  int ms = secs*1000;
  SDL_Delay(ms);
)

JSC_CCALL(os_power_state,
  int pct, secs;
  SDL_PowerState state = SDL_GetPowerInfo(&secs, &pct);
  const char *statestr = "unknown";
  switch(state) {
    case SDL_POWERSTATE_ON_BATTERY: statestr = "battery"; break;
    case SDL_POWERSTATE_NO_BATTERY: statestr = "no battery"; break;
    case SDL_POWERSTATE_CHARGING: statestr = "charging"; break;
    case SDL_POWERSTATE_CHARGED: statestr = "charged"; break;
  }
  ret = JS_NewObject(js);
  JS_SetPropertyStr(js,ret,"state",JS_NewString(js,statestr));
  JS_SetPropertyStr(js,ret,"percent",number2js(js,pct));
  JS_SetPropertyStr(js,ret,"seconds",number2js(js,secs));
)

JSC_CCALL(os_totalmem, return number2js(js, SDL_GetSystemRAM()))
JSC_CCALL(os_platform, return JS_NewString(js,SDL_GetPlatform()))

static JSValue js_os_hostname(JSContext *js, JSValue self, int argc, JSValue *argv) {
  JSValue ret = JS_NULL;
#ifdef _WIN32
  TCHAR buffer[256] = TEXT("");
  DWORD size = sizeof(buffer) / sizeof(TCHAR);
  GetComputerName(buffer, &size);
  return JS_NewString(js, buffer);
#else
  struct utsname buffer;
  if (uname(&buffer) != 0) return JS_ThrowReferenceError(js,"Could not get hostname.");
  return JS_NewString(js, buffer.nodename);
#endif
  return ret;
}

static JSValue js_os_arch(JSContext *js, JSValue self, int argc, JSValue *argv) {
  JSValue ret = JS_NULL;
#if defined(__x86_64__) || defined(_M_X64)
  return JS_NewString(js,"x64");
#elif defined(__aarch64__) || defined(_M_ARM64)
  return JS_NewString(js,"arm64");
#elif defined(__arm__) || defined(_M_ARM)
  return JS_NewString(js,"arm");
#elif defined(__i386__) || defined(_M_IX86)
  return JS_NewString(js,"ia32");
#elif defined(__loongarch__) || defined(__loongarch32) || defined(__loongarch64)
  return JS_NewString(js,"loong64");
#elif defined(__mips__) || defined(__mips) || defined(_M_MIPS)
  // You might want to distinguish mips vs mipsel
  return JS_NewString(js,"mips");
#elif defined(__ppc64__) || defined(__powerpc64__) || defined(_M_PPC)
  // You might want to distinguish ppc vs ppc64, big-endian vs little-endian
  return JS_NewString(js,"ppc64");
#elif defined(__riscv) && __riscv_xlen == 64
  return JS_NewString(js,"riscv64");
#elif defined(__s390x__)
  return JS_NewString(js,"s390x");
#else
  return JS_NewString(js,"unknown");
#endif
  return ret;
}

static JSValue js_os_freemem(JSContext *js, JSValue self, int argc, JSValue *argv) {
  JSValue ret = JS_NULL;
#ifdef _WIN32
  MEMORYSTATUSEX statex;
  statex.dwLength = sizeof(statex);
  if (!GlobalMemoryStatusEx(&statex)) return JS_ThrowInternalError(js,"GlobalMemoryStatusEx failed");
  return JS_NewInt64(js,(int64_t)statex.ullAvailPhys);
#elif defined(__linux__)
  struct sysinfo info;
  if (sysinfo(&info) == 0)
    return JS_NewInt64(js,(int64_t)info.freeram * info.mem_unit);
  return JS_NewInt64(js,0);
#elif defined(__FreeBSD__) || defined(__OpenBSD__)
  // A very rough fallback using the same sysconf approach
  // (macOS or *BSD typically need specialized APIs to get free mem accurately)
  // This is often only "unused" pages, ignoring caches, etc.
  long pages = sysconf(_SC_AVPHYS_PAGES);
  long page_size = sysconf(_SC_PAGE_SIZE);
  if (pages < 0 || page_size < 0) return JS_NewInt64(js,0);
  return JS_NewInt64(js,(int64_t)pages * (int64_t)page_size);
#else
  // Fallback: unknown
  return JS_NewInt64(js,0);
#endif
  return ret;
}

static JSValue js_os_version(JSContext *js, JSValue self, int argc, JSValue *argv) {
  JSValue ret = JS_NULL;
#ifdef _WIN32
  typedef LONG (WINAPI *RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
  HMODULE h = GetModuleHandleA("ntdll.dll");
  if (h) {
    RtlGetVersionPtr fx = (RtlGetVersionPtr)GetProcAddress(h, "RtlGetVersion");
    if (fx) {
      RTL_OSVERSIONINFOW ver;
      memset(&ver, 0, sizeof(ver));
      ver.dwOSVersionInfoSize = sizeof(ver);
      if (!fx(&ver)) {
        char buf[128];
        sprintf(buf, "%u.%u.%u",
          (unsigned)ver.dwMajorVersion,
          (unsigned)ver.dwMinorVersion,
          (unsigned)ver.dwBuildNumber
        );
        return JS_NewString(js, buf);
      }
    }
  }
  OSVERSIONINFOW wver;
  memset(&wver, 0, sizeof(wver));
  wver.dwOSVersionInfoSize = sizeof(wver);
  if (GetVersionExW(&wver)) {
    char buf[128];
    sprintf(buf, "%u.%u.%u",
      (unsigned)wver.dwMajorVersion,
      (unsigned)wver.dwMinorVersion,
      (unsigned)wver.dwBuildNumber
    );
    return JS_NewString(js, buf);
  }
  return JS_NewString(js, "Windows_Unknown");
#else
  struct utsname info;
  if (!uname(&info)) return JS_NewString(js, info.release);
  return JS_NewString(js, "");
#endif
  return ret;
}

JSC_CCALL(os_buffer2string,
  if (argc < 1) {
    return JS_ThrowTypeError(js, "buffer2string expects an ArrayBuffer");
  }
  
  size_t len;
  uint8_t *buf = js_get_blob_data(js, &len, argv[0]);
  if (!buf) {
    return JS_ThrowTypeError(js, "First argument must be an ArrayBuffer");
  }
  
  // Create a null-terminated string from the buffer
  char *str = js_malloc(js, len + 1);
  if (!str) {
    return JS_ThrowInternalError(js, "Failed to allocate memory");
  }
  
  memcpy(str, buf, len);
  str[len] = '\0';
  
  JSValue result = JS_NewString(js, str);
  js_free(js, str);
  
  return result;
)

#define JSOBJ_ADD_FIELD(OBJ, STRUCT, FIELD, TYPE) \
JS_SetPropertyStr(js, OBJ, #FIELD, TYPE##2js(js,STRUCT.FIELD));\

#define JSJMEMRET(FIELD) JSOBJ_ADD_FIELD(ret, jsmem, FIELD, number)

JSC_CCALL(os_mallinfo,
  ret = JS_NULL;
  /*struct mallinfo jsmem = mallinfo();
  ret = JS_NewObject(js);
  JSJMEMRET(arena);
  JSJMEMRET(ordblks);
  JSJMEMRET(smblks);
  JSJMEMRET(hblks);
  JSJMEMRET(hblkhd);
  JSJMEMRET(usmblks);
  JSJMEMRET(fsmblks);
  JSJMEMRET(uordblks);
  JSJMEMRET(fordblks);
  JSJMEMRET(keepcost);*/
)

static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) {
  JSValue ret = JS_NULL;
  ret = JS_NewObject(js);

#if defined(__linux__) || defined(__APPLE__)
  struct rusage jsmem;
  getrusage(RUSAGE_SELF, &jsmem);
  JSJMEMRET(ru_maxrss);
  JSJMEMRET(ru_ixrss);
  JSJMEMRET(ru_idrss);
  JSJMEMRET(ru_isrss);
  JSJMEMRET(ru_minflt);
  JSJMEMRET(ru_majflt);
  JSJMEMRET(ru_nswap);
  JSJMEMRET(ru_inblock);
  JSJMEMRET(ru_oublock);
  JSJMEMRET(ru_msgsnd);
  JSJMEMRET(ru_msgrcv);
  JSJMEMRET(ru_nsignals);
  JSJMEMRET(ru_nvcsw);
  JSJMEMRET(ru_nivcsw);
#endif

  return ret;
}

#ifdef TRACY_ENABLE
#include <tracy/TracyC.h>
JSC_CCALL(os_frame,
  if (!tracy_profiling_enabled) return JS_NULL;
  TracyCFrameMark
)

JSC_CCALL(os_trace_img,
  if (!tracy_profiling_enabled) return JS_NULL;
  size_t len;
  double width, height;
  JS_ToFloat64(js,&width,argv[1]);
  JS_ToFloat64(js,&height,argv[2]);
  ___tracy_emit_frame_image(js_get_blob_data(js, &len, argv[0]), width, height, 0, 0);
)

JSC_CCALL(os_trace_message,
  if (!tracy_profiling_enabled) return JS_NULL;
  size_t len;
  const char *str = JS_ToCStringLen(js, &len, argv[0]);
  TracyCMessage(str,len);
  JS_FreeCString(js,str);
)
#else
JSC_CCALL(os_frame,
)

JSC_CCALL(os_trace_img,
)

JSC_CCALL(os_trace_message,
)
#endif

static const JSCFunctionListEntry js_os_funcs[] = {
  MIST_FUNC_DEF(os, platform, 0),
  MIST_FUNC_DEF(os, arch, 0),
  MIST_FUNC_DEF(os, totalmem, 0),
  MIST_FUNC_DEF(os, freemem, 0),
  MIST_FUNC_DEF(os, hostname, 0),
  MIST_FUNC_DEF(os, version, 0),
  MIST_FUNC_DEF(os, now, 0),
  MIST_FUNC_DEF(os, power_state, 0),
  MIST_FUNC_DEF(os, rusage, 0),
  MIST_FUNC_DEF(os, mallinfo, 0),
  MIST_FUNC_DEF(os, buffer2string, 1),
  MIST_FUNC_DEF(os, frame, 0),
};

JSValue js_os_use(JSContext *js) {
  JSValue mod = JS_NewObject(js);
  JS_SetPropertyFunctionList(js,mod,js_os_funcs,countof(js_os_funcs));
  return mod;
}
