/*
# This file is part of the Astrometry.net suite.
# Licensed under a 3-clause BSD style license - see LICENSE
*/
#ifndef OS_FEATURES_H
#define OS_FEATURES_H

#ifndef DONT_INCLUDE_OS_FEATURES_CONFIG_H
#include "astrometry/os-features-config.h"
#endif

//# Modified by Robert Lancaster for the StellarSolver Internal Library
// Features we use that aren't standard across all supported platforms
//#if defined(NEED_CANONICALIZE_FILE_NAME) && (NEED_CANONICALIZE_FILE_NAME == 0)
// don't redeclare it!
//#else
//char* canonicalize_file_name(const char* fn);
//#endif

// This is actually in POSIX1b but may or may not be available.
//int fdatasync(int fd); //# Modified by Robert Lancaster for the StellarSolver Internal Library

// Not POSIX; doesn't exist in Solaris 10 or MSVC
#ifndef _MSC_VER //# Modified by Robert Lancaster for the StellarSolver Internal Library
#include <sys/param.h>
#endif
#ifndef MIN
#define	MIN(a,b) (((a)<(b))?(a):(b))
#endif
#ifndef MAX
#define	MAX(a,b) (((a)>(b))?(a):(b))
#endif

// isfinite() on Solaris; from
// https://code.google.com/p/redis/issues/detail?id=20
#if defined(__sun) && defined(__GNUC__)

#undef isnan
#define isnan(x) \
      __extension__({ __typeof (x) __x_a = (x); \
      __builtin_expect(__x_a != __x_a, 0); })

#undef isfinite
#define isfinite(x) \
      __extension__ ({ __typeof (x) __x_f = (x); \
      __builtin_expect(!isnan(__x_f - __x_f), 1); })

#undef isinf
#define isinf(x) \
      __extension__ ({ __typeof (x) __x_i = (x); \
      __builtin_expect(!isnan(__x_i) && !isfinite(__x_i), 0); })

#undef isnormal
#define isnormal(x) \
  __extension__ ({ __typeof(x) __x_n = (x); \
                   if (__x_n < 0.0) __x_n = -__x_n; \
                   __builtin_expect(isfinite(__x_n) \
                                    && (sizeof(__x_n) == sizeof(float) \
                                          ? __x_n >= __FLT_MIN__ \
                                          : sizeof(__x_n) == sizeof(long double) \
                                            ? __x_n >= __LDBL_MIN__ \
                                            : __x_n >= __DBL_MIN__), 1); })

#undef HUGE_VALF
#define HUGE_VALF (1e50f)

#endif

/**
   The qsort_r story:

   -qsort_r appears in BSD (including Mac OSX)
         void qsort_r(void *, size_t, size_t,
                      void *,
                      int (*)(void *, const void *, const void *));

   -qsort_r appears in glibc 2.8, but with a different argument order:
         void qsort_r(void*, size_t, size_t,
                      int (*)(const void*, const void*, void*),
                      void*);

   Notice that the "thunk" and "comparison function" arguments to qsort_r are
   swapped, and the "thunk" appears either at the beginning or end of the comparison
   function.

   We check a few things:
   -is qsort_r declared?
   -does qsort_r exist?
   -do we need to swap the arguments?

   Those using qsort_r in Astrometry.net should instead use the macro QSORT_R()
   to take advantage of these tests.

   Its signature is:

   void QSORT_R(void* base, size_t nmembers, size_t member_size,
                void* token, comparison_function);

   You should define the "comparison" function like this:

   static int QSORT_COMPARISON_FUNCTION(my_comparison, void* token, const void* v1, const void* v2) {
     ...
   }


   Distributions including glibc 2.8 include:
   -Mandriva 2009
   -Ubuntu 8.10
*/

#if NEED_DECLARE_QSORT_R
//// NOTE: this declaration must match os-features-test.c .
void qsort_r(void *base, size_t nmemb, size_t sz,
             void *userdata,
             int (*compar)(void *, const void *, const void *));
#endif

#if NEED_SWAP_QSORT_R
#define QSORT_R(a,b,c,d,e) qsort_r(a,b,c,e,d)
#define QSORT_COMPARISON_FUNCTION(func, thunk, v1, v2) func(v1, v2, thunk)

#else
#define QSORT_R qsort_r
#define QSORT_COMPARISON_FUNCTION(func, thunk, v1, v2) func(thunk, v1, v2)

#endif

//# Modified for the StellarSolver Internal Library for thread safety
// Portable reentrant wrappers for platform-specific APIs.
// Each wraps the platform's reentrant variant behind a uniform interface.
// _WIN32 selects the Windows path (MSVC and MinGW).
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <errno.h>

// --- portable_qsort_r ---
// Uses GNU-style comparator: compar(v1, v2, arg) with thunk last.
// Adapters handle BSD (thunk-first) and Windows (qsort_s) conventions.

struct portable_qsort_r_adapter {
    int (*compar)(const void *, const void *, void *);
    void *arg;
};

#if defined(_WIN32)
static inline int portable_qsort_r_win_adapter(void *context, const void *v1, const void *v2) {
    struct portable_qsort_r_adapter *adapter = (struct portable_qsort_r_adapter *)context;
    return adapter->compar(v1, v2, adapter->arg);
}
static inline void portable_qsort_r(void *base, size_t nmemb, size_t size,
                                    int (*compar)(const void *, const void *, void *),
                                    void *arg) {
    struct portable_qsort_r_adapter adapter;
    adapter.compar = compar;
    adapter.arg = arg;
    qsort_s(base, nmemb, size, portable_qsort_r_win_adapter, &adapter);
}

#elif defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
static inline int portable_qsort_r_bsd_adapter(void *thunk, const void *v1, const void *v2) {
    struct portable_qsort_r_adapter *adapter = (struct portable_qsort_r_adapter *)thunk;
    return adapter->compar(v1, v2, adapter->arg);
}
static inline void portable_qsort_r(void *base, size_t nmemb, size_t size,
                                    int (*compar)(const void *, const void *, void *),
                                    void *arg) {
    struct portable_qsort_r_adapter adapter;
    adapter.compar = compar;
    adapter.arg = arg;
    qsort_r(base, nmemb, size, &adapter, portable_qsort_r_bsd_adapter);
}

#else
static inline void portable_qsort_r(void *base, size_t nmemb, size_t size,
                                    int (*compar)(const void *, const void *, void *),
                                    void *arg) {
    qsort_r(base, nmemb, size, compar, arg);
}
#endif

// --- portable_strtok_r ---
static inline char* portable_strtok_r(char *str, const char *delim, char **saveptr) {
#ifdef _WIN32
    return strtok_s(str, delim, saveptr);
#else
    return strtok_r(str, delim, saveptr);
#endif
}

// --- portable_localtime_r ---
// Returns result on success, NULL on failure (uniform across platforms).
static inline struct tm* portable_localtime_r(const time_t *timep, struct tm *result) {
#ifdef _WIN32
    return localtime_s(result, timep) == 0 ? result : NULL;
#else
    return localtime_r(timep, result);
#endif
}

// --- portable_strerror_r ---
// Always fills buf. Falls back to the error number on failure.
// Handles GNU strerror_r (returns char*) vs POSIX strerror_r (returns int).
static inline void portable_strerror_r(int errnum, char *buf, size_t buflen) {
#if defined(_WIN32)
    if (strerror_s(buf, buflen, errnum) != 0)
        snprintf(buf, buflen, "errno %d", errnum);
#elif defined(_GNU_SOURCE) || (defined(__GLIBC__) && defined(__USE_GNU))
    char *res = strerror_r(errnum, buf, buflen);
    if (res != buf) {
        strncpy(buf, res, buflen - 1);
        buf[buflen - 1] = '\0';
    }
#else
    if (strerror_r(errnum, buf, buflen) != 0)
        snprintf(buf, buflen, "errno %d", errnum);
#endif
}

// As suggested in http://gcc.gnu.org/onlinedocs/gcc-4.3.0/gcc/Function-Names.html
#if __STDC_VERSION__ < 199901L
# if __GNUC__ >= 2
#  define __func__ __FUNCTION__
# else
#  define __func__ "<unknown>"
# endif
#endif

#endif
