/** \file
 * \brief macOS System Information
 *
 * See Copyright Notice in "iup.h"
 */

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

#import <Cocoa/Cocoa.h>
#import <CoreGraphics/CoreGraphics.h>
#ifndef GNUSTEP
#import <SystemConfiguration/SystemConfiguration.h>
#import <os/log.h>
#endif

#include <sys/utsname.h>
#include <unistd.h>
#include <langinfo.h>

#include "iup.h"
#include "iup_varg.h"
#include "iup_str.h"
#include "iup_drvinfo.h"
#include "iup_class.h"
#include "iup_classbase.h"


IUP_SDK_API char* iupdrvGetCurrentDirectory(void)
{
  NSString *curDir = [[NSFileManager defaultManager] currentDirectoryPath];
  return iupStrReturnStr([curDir UTF8String]);
}

IUP_SDK_API int iupdrvSetCurrentDirectory(const char* dir)
{
  NSString *path = [NSString stringWithUTF8String:dir];
  BOOL r = [[NSFileManager defaultManager] changeCurrentDirectoryPath:path];
  return (r ? 1 : 0);
}

IUP_SDK_API void iupdrvAddScreenOffset(int *x, int *y, int add)
{
  NSScreen *screen = [NSScreen mainScreen];
  if (screen)
  {
    NSRect frame = [screen frame];
    NSRect visibleFrame = [screen visibleFrame];

    /* In IUP's top-left coordinate system, the offset is the top-left corner of the visible area (work area). */
    int offset_x = visibleFrame.origin.x;
    /* The y-offset is the height of the main menu bar. */
    int offset_y = frame.size.height - (visibleFrame.origin.y + visibleFrame.size.height);

    /* add > 0 means add offset, add < 0 means subtract offset, add == 0 means no change */
    if (add > 0)
    {
      if (x) *x += offset_x;
      if (y) *y += offset_y;
    }
    else if (add < 0)
    {
      if (x) *x -= offset_x;
      if (y) *y -= offset_y;
    }
  }
}

IUP_SDK_API void iupdrvGetScreenSize(int *width, int *height)
{
  /* Returns the usable screen area, excluding the menu bar and dock. */
  NSRect screen_rect = [[NSScreen mainScreen] visibleFrame];
  if (width) *width = (int)screen_rect.size.width;
  if (height) *height = (int)screen_rect.size.height;
}

IUP_SDK_API void iupdrvGetFullSize(int *width, int *height)
{
  NSRect screen_rect = [[NSScreen mainScreen] frame];
  if (width) *width = (int)screen_rect.size.width;
  if (height) *height = (int)screen_rect.size.height;
}

IUP_SDK_API int iupdrvGetScreenDepth(void)
{
  NSScreen* screen = [NSScreen mainScreen];
  if (screen)
  {
    NSDictionary* deviceDescription = [screen deviceDescription];
    NSNumber* bitsPerSample = [deviceDescription objectForKey:@"NSDeviceBitsPerSample"];
    if (bitsPerSample)
    {
      int bits = [bitsPerSample intValue];
      return bits * 4;
    }
  }
  return 32;
}

IUP_SDK_API double iupdrvGetScreenDpi(void)
{
  NSScreen* screen = [NSScreen mainScreen];
  if (screen != nil)
  {
    NSDictionary* deviceDescription = [screen deviceDescription];
    NSSize resolution = [[deviceDescription objectForKey:NSDeviceResolution] sizeValue];
    return resolution.height;
  }
  return 72.0;
}

IUP_SDK_API int iupdrvScaleNaturalPx(int px)
{
  return px;
}

IUP_SDK_API void iupdrvGetCursorPos(int *x, int *y)
{
  /* [NSEvent mouseLocation] origin is bottom-left of the primary screen. */
  NSPoint mouse_point = [NSEvent mouseLocation];

  /* Invert the y-axis for IUP's top-left origin system. */
  NSRect screen_rect = [[NSScreen mainScreen] frame];
  CGFloat inverted_y = screen_rect.size.height - mouse_point.y;

  if (x) *x = iupROUND(mouse_point.x);
  if (y) *y = iupROUND(inverted_y);

  iupdrvAddScreenOffset(x, y, -1);
}

IUP_SDK_API void iupdrvGetKeyState(char* key)
{
  NSEventModifierFlags flags = [NSEvent modifierFlags];

  key[0] = (flags & NSEventModifierFlagShift)   ? 'S' : ' ';
  key[1] = (flags & NSEventModifierFlagControl) ? 'C' : ' ';
  key[2] = (flags & NSEventModifierFlagOption)  ? 'A' : ' '; /* Alt Key */
  key[3] = (flags & NSEventModifierFlagCommand) ? 'Y' : ' '; /* System Key */
  key[4] = 0;
}

IUP_SDK_API char *iupdrvGetSystemName(void)
{
#ifndef GNUSTEP
  NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
  const char* codename = NULL;

  /* Starting with macOS 11 (Big Sur), the major version number is the primary identifier. */
  if (version.majorVersion >= 11)
  {
    switch (version.majorVersion)
    {
      case 26: codename = "Tahoe"; break;
      case 15: codename = "Sequoia"; break;
      case 14: codename = "Sonoma"; break;
      case 13: codename = "Ventura"; break;
      case 12: codename = "Monterey"; break;
      case 11: codename = "Big Sur"; break;
    }
  }
  /* For older macOS versions (10.x), check the minor version. */
  else if (version.majorVersion == 10)
  {
    switch (version.minorVersion)
    {
      case 15: codename = "Catalina"; break;
      case 14: codename = "Mojave"; break;
      case 13: codename = "High Sierra"; break;
      case 12: codename = "Sierra"; break;
      case 11: codename = "El Capitan"; break;
      case 10: codename = "Yosemite"; break;
    }
  }

  char buffer[100];
  if (codename)
  {
    snprintf(buffer, sizeof(buffer), "macOS %s", codename);
  }
  else
  {
    iupStrCopyN(buffer, sizeof(buffer), "macOS");
  }

  return iupStrReturnStr(buffer);
#else
  struct utsname systemInfo;
  if (uname(&systemInfo) == 0)
    return iupStrReturnStr(systemInfo.sysname);
  return iupStrReturnStr("Linux");
#endif
}

IUP_SDK_API char *iupdrvGetSystemVersion(void)
{
  char* str = iupStrGetMemory(100);

#ifndef GNUSTEP
  NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion];
  snprintf(str, 100, "%ld.%ld.%ld", (long)version.majorVersion, (long)version.minorVersion, (long)version.patchVersion);

  /* Append architecture info. */
  struct utsname systemInfo;
  if (uname(&systemInfo) == 0)
  {
    snprintf(str + strlen(str), 100 - strlen(str), " (%s)", systemInfo.machine);
  }
#else
  struct utsname systemInfo;
  if (uname(&systemInfo) == 0)
    snprintf(str, 100, "%s (%s)", systemInfo.release, systemInfo.machine);
  else
    iupStrCopyN(str, 100, "unknown");
#endif

  return str;
}

IUP_SDK_API char *iupdrvGetComputerName(void)
{
#ifndef GNUSTEP
  NSString* computer_name = [(NSString *)SCDynamicStoreCopyComputerName(NULL, NULL) autorelease];
  if (!computer_name)
    return NULL;

  return iupStrReturnStr([computer_name UTF8String]);
#else
  char hostname[256];
  if (gethostname(hostname, sizeof(hostname)) == 0)
    return iupStrReturnStr(hostname);
  return NULL;
#endif
}

IUP_SDK_API char *iupdrvGetUserName(void)
{
  NSString* user_name = NSUserName();
  if (!user_name)
    return NULL;

  return iupStrReturnStr([user_name UTF8String]);
}

IUP_SDK_API int iupdrvGetPreferencePath(char *filename, const char *app_name, int use_system)
{
  if (!app_name || !app_name[0])
  {
    filename[0] = '\0';
    return 0;
  }

  if (use_system)
  {
    char root[10240];
    if (!iupdrvGetUserDir(root, sizeof(root), IUP_USER_DIR_CONFIG))
    {
      filename[0] = '\0';
      return 0;
    }
    NSString* ns_path = [[NSString stringWithUTF8String:root] stringByAppendingPathComponent:[NSString stringWithUTF8String:app_name]];
    [[NSFileManager defaultManager] createDirectoryAtPath:ns_path withIntermediateDirectories:YES attributes:nil error:nil];
    NSString* ns_config_path = [ns_path stringByAppendingPathComponent:@"config"];
    const char* c_path = [ns_config_path fileSystemRepresentation];
    if (!c_path)
    {
      filename[0] = '\0';
      return 0;
    }
    iupStrCopyN(filename, 10240, c_path);
    return 1;
  }

  NSString* home_path = NSHomeDirectory();
  if (home_path)
  {
    NSString* ns_path = [home_path stringByAppendingPathComponent:[NSString stringWithFormat:@".%s", app_name]];
    const char* c_path = [ns_path fileSystemRepresentation];
    if (c_path)
    {
      iupStrCopyN(filename, 10240, c_path);
      return 1;
    }
  }

  filename[0] = '\0';
  return 0;
}

IUP_SDK_API int iupdrvGetUserDir(char* path, int size, int kind)
{
  NSSearchPathDirectory dir;

  if (!path || size <= 0)
    return 0;
  path[0] = '\0';

  if (kind == IUP_USER_DIR_TEMP)
  {
    NSString* tmp = NSTemporaryDirectory();
    if (!tmp) return 0;
    NSString* trimmed = [tmp hasSuffix:@"/"] ? [tmp substringToIndex:tmp.length - 1] : tmp;
    const char* c_path = [trimmed fileSystemRepresentation];
    if (!c_path) return 0;
    iupStrCopyN(path, size, c_path);
    return 1;
  }

  switch (kind)
  {
    case IUP_USER_DIR_CACHE:  dir = NSCachesDirectory; break;
    case IUP_USER_DIR_DATA:
    case IUP_USER_DIR_CONFIG: dir = NSApplicationSupportDirectory; break;
    default: return 0;
  }

  NSArray* roots = NSSearchPathForDirectoriesInDomains(dir, NSUserDomainMask, YES);
  if ([roots count] == 0) return 0;
  const char* c_path = [[roots objectAtIndex:0] fileSystemRepresentation];
  if (!c_path) return 0;
  iupStrCopyN(path, size, c_path);
  return 1;
}

IUP_SDK_API char* iupdrvLocaleInfo(void)
{
  return iupStrReturnStr(nl_langinfo(CODESET));
}

IUP_API void IupLogV(const char* type, const char* format, va_list arglist)
{
  char buffer[2048];
  vsnprintf(buffer, sizeof(buffer), format, arglist);

#ifndef GNUSTEP
  os_log_type_t log_type = OS_LOG_TYPE_DEFAULT;
  if (iupStrEqualNoCase(type, "DEBUG"))
    log_type = OS_LOG_TYPE_DEBUG;
  else if (iupStrEqualNoCase(type, "INFO"))
    log_type = OS_LOG_TYPE_INFO;
  else if (iupStrEqualNoCase(type, "WARNING"))
    log_type = OS_LOG_TYPE_ERROR;
  else if (iupStrEqualNoCase(type, "ERROR"))
    log_type = OS_LOG_TYPE_ERROR;
  else if (iupStrEqualNoCase(type, "CRITICAL") || iupStrEqualNoCase(type, "ALERT") || iupStrEqualNoCase(type, "EMERGENCY"))
    log_type = OS_LOG_TYPE_FAULT;

  /* os_log requires a static format string for privacy, so we must mark dynamic content as public. */
  os_log_with_type(OS_LOG_DEFAULT, log_type, "%{public}s", buffer);
#else
  fprintf(stderr, "[%s] %s\n", type ? type : "LOG", buffer);
#endif
}

IUP_API void IupLog(const char* type, const char* format, ...)
{
  va_list arglist;
  va_start(arglist, format);
  IupLogV(type, format, arglist);
  va_end(arglist);
}
