/*
    This file is part of darktable,
    Copyright (C) 2009-2021 darktable developers.

    darktable is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    darktable is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with darktable.  If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once

// just to be sure. the build system should set this for us already:
#if defined __DragonFly__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__
#define _WITH_DPRINTF
#define _WITH_GETLINE
#elif !defined _XOPEN_SOURCE && !defined _WIN32
#define _XOPEN_SOURCE 700 // for localtime_r and dprintf
#endif

// needs to be defined before any system header includes for control/conf.h to work in C++ code
#define __STDC_FORMAT_MACROS

#if defined _WIN32
#include "win/win.h"
#endif

#if !defined(O_BINARY)
// To have portable g_open() on *nix and on Windows
#define O_BINARY 0
#endif

#include "external/ThreadSafetyAnalysis.h"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "common/database.h"
#include "common/dtpthread.h"
#include "common/dttypes.h"
#include "common/utility.h"
#include "common/wb_presets.h"
#ifdef _WIN32
#include "win/getrusage.h"
#else
#include <sys/resource.h>
#endif
#include <glib.h>
#include <glib/gstdio.h>
#include <glib/gi18n.h>
#include <inttypes.h>
#include <json-glib/json-glib.h>
#include <lua/lua.h>
#include <math.h>
#include <sqlite3.h>
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#ifdef __APPLE__
#include <mach/mach.h>
#include <sys/sysctl.h>
#endif

#if defined(__DragonFly__) || defined(__FreeBSD__)
typedef unsigned int u_int;
#include <sys/sysctl.h>
#include <sys/types.h>
#endif
#if defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/param.h>
#include <sys/sysctl.h>
#endif

#ifdef _OPENMP
# include <omp.h>

/* See https://redmine.darktable.org/issues/12568#note-14 */
# ifdef HAVE_OMP_FIRSTPRIVATE_WITH_CONST
   /* If the compiler correctly supports firstprivate, use it. */
#  define dt_omp_firstprivate(...) firstprivate(__VA_ARGS__)
# else /* HAVE_OMP_FIRSTPRIVATE_WITH_CONST */
   /* This is needed for clang < 7.0 */
#  define dt_omp_firstprivate(...)
# endif/* HAVE_OMP_FIRSTPRIVATE_WITH_CONST */

#ifndef dt_omp_sharedconst
#ifdef _OPENMP
#if defined(__clang__) || __GNUC__ > 8
# define dt_omp_sharedconst(...) shared(__VA_ARGS__)
#else
  // GCC 8.4 throws string of errors "'x' is predetermined 'shared' for 'shared'" if we explicitly declare
  //  'const' variables as shared
# define dt_omp_sharedconst(var, ...)
#endif
#endif /* _OPENMP */
#endif /* dt_omp_sharedconst */

#ifndef dt_omp_nontemporal
// Clang 10+ supports the nontemporal() OpenMP directive
// GCC 9 recognizes it as valid, but does not do anything with it
// GCC 10+ ???
#if (__clang__+0 >= 10 || __GNUC__ >= 9)
#  define dt_omp_nontemporal(...) nontemporal(__VA_ARGS__)
#else
// GCC7/8 only support OpenMP 4.5, which does not have the nontemporal() directive.
#  define dt_omp_nontemporal(var, ...)
#endif
#endif /* dt_omp_nontemporal */

#else /* _OPENMP */

# define omp_get_max_threads() 1
# define omp_get_thread_num() 0

#endif /* _OPENMP */

/* Create cloned functions for various CPU SSE generations */
/* See for instructions https://hannes.hauswedell.net/post/2017/12/09/fmv/ */
/* TL;DR : use only on SIMD functions containing low-level paralellized/vectorized loops */
#if __has_attribute(target_clones) && !defined(_WIN32) && !defined(NATIVE_ARCH)
# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64)
#define __DT_CLONE_TARGETS__ __attribute__((target_clones("default", "sse2", "sse3", "sse4.1", "sse4.2", "popcnt", "avx", "avx2", "avx512f", "fma4")))
# elif defined(__PPC64__)
/* __PPC64__ is the only macro tested for in is_supported_platform.h, other macros would fail there anyway. */
#define __DT_CLONE_TARGETS__ __attribute__((target_clones("default","cpu=power9")))
# else
#define __DT_CLONE_TARGETS__
# endif
#else
#define __DT_CLONE_TARGETS__
#endif

/* Helper to force stack vectors to be aligned on 64 bits blocks to enable AVX2 */
#define DT_IS_ALIGNED(x) __builtin_assume_aligned(x, 64)

#ifndef _RELEASE
#include "common/poison.h"
#endif

#include "common/usermanual_url.h"

// for signal debugging symbols
#include "control/signal.h"

#define DT_MODULE_VERSION 23 // version of dt's module interface

// version of current performance configuration version
// if you want to run an updated version of the performance configuration later
// bump this number and make sure you have an updated logic in dt_configure_performance()
#define DT_CURRENT_PERFORMANCE_CONFIGURE_VERSION 12
#define DT_PERF_INFOSIZE 4096

// every module has to define this:
#ifdef _DEBUG
#define DT_MODULE(MODVER)                                                                                    \
  int dt_module_dt_version()                                                                                 \
  {                                                                                                          \
    return -DT_MODULE_VERSION;                                                                               \
  }                                                                                                          \
  int dt_module_mod_version()                                                                                \
  {                                                                                                          \
    return MODVER;                                                                                           \
  }
#else
#define DT_MODULE(MODVER)                                                                                    \
  int dt_module_dt_version()                                                                                 \
  {                                                                                                          \
    return DT_MODULE_VERSION;                                                                                \
  }                                                                                                          \
  int dt_module_mod_version()                                                                                \
  {                                                                                                          \
    return MODVER;                                                                                           \
  }
#endif

#define DT_MODULE_INTROSPECTION(MODVER, PARAMSTYPE) DT_MODULE(MODVER)

// ..to be able to compare it against this:
static inline int dt_version()
{
#ifdef _DEBUG
  return -DT_MODULE_VERSION;
#else
  return DT_MODULE_VERSION;
#endif
}

// returns true if the running darktable corresponds to a dev version
gboolean dt_is_dev_version();
// returns the darktable version as <major>.<minor>
char *dt_version_major_minor();

// Golden number (1+sqrt(5))/2
#ifndef PHI
#define PHI 1.61803398874989479F
#endif

// 1/PHI
#ifndef INVPHI
#define INVPHI 0.61803398874989479F
#endif

// NaN-safe clamping (NaN compares false, and will thus result in H)
#define CLAMPS(A, L, H) ((A) > (L) ? ((A) < (H) ? (A) : (H)) : (L))

#undef STR_HELPER
#define STR_HELPER(x) #x

#undef STR
#define STR(x) STR_HELPER(x)

#define DT_IMAGE_DBLOCKS 64

// If platform supports hardware-accelerated fused-multiply-add
// This is not only faster but more accurate because rounding happens at the right place
#ifdef FP_FAST_FMAF
  #define DT_FMA(x, y, z) fmaf(x, y, z)
#else
  #define DT_FMA(x, y, z) ((x) * (y) + (z))
#endif

struct dt_gui_gtk_t;
struct dt_control_t;
struct dt_develop_t;
struct dt_mipmap_cache_t;
struct dt_image_cache_t;
struct dt_lib_t;
struct dt_conf_t;
struct dt_points_t;
struct dt_imageio_t;
struct dt_bauhaus_t;
struct dt_undo_t;
struct dt_colorspaces_t;
struct dt_l10n_t;

typedef float dt_boundingbox_t[4];  //(x,y) of upperleft, then (x,y) of lowerright

typedef enum dt_debug_thread_t
{
  // powers of two, masking
  DT_DEBUG_CACHE          = 1 <<  0,
  DT_DEBUG_CONTROL        = 1 <<  1,
  DT_DEBUG_DEV            = 1 <<  2,
  DT_DEBUG_PERF           = 1 <<  4,
  DT_DEBUG_CAMCTL         = 1 <<  5,
  DT_DEBUG_PWSTORAGE      = 1 <<  6,
  DT_DEBUG_OPENCL         = 1 <<  7,
  DT_DEBUG_SQL            = 1 <<  8,
  DT_DEBUG_MEMORY         = 1 <<  9,
  DT_DEBUG_LIGHTTABLE     = 1 << 10,
  DT_DEBUG_NAN            = 1 << 11,
  DT_DEBUG_MASKS          = 1 << 12,
  DT_DEBUG_LUA            = 1 << 13,
  DT_DEBUG_INPUT          = 1 << 14,
  DT_DEBUG_PRINT          = 1 << 15,
  DT_DEBUG_CAMERA_SUPPORT = 1 << 16,
  DT_DEBUG_IOPORDER       = 1 << 17,
  DT_DEBUG_IMAGEIO        = 1 << 18,
  DT_DEBUG_UNDO           = 1 << 19,
  DT_DEBUG_SIGNAL         = 1 << 20,
  DT_DEBUG_PARAMS         = 1 << 21,
  DT_DEBUG_DEMOSAIC       = 1 << 22,
  DT_DEBUG_ACT_ON         = 1 << 23,
  DT_DEBUG_TILING         = 1 << 24,
  DT_DEBUG_VERBOSE        = 1 << 25,
  DT_DEBUG_ROI            = 1 << 26
} dt_debug_thread_t;

typedef struct dt_codepath_t
{
  unsigned int SSE2 : 1;
  unsigned int _no_intrinsics : 1;
  unsigned int OPENMP_SIMD : 1; // always stays the last one
} dt_codepath_t;

typedef struct dt_sys_resources_t
{
  size_t total_memory;
  size_t mipmap_memory;
  int *fractions;   // fractions are calculated as res=input / 1024  * fraction
  int *refresource; // for the debug resource modes we use fixed settings
  int group;
  int level;
  int tunemode;
} dt_sys_resources_t;

typedef struct darktable_t
{
  dt_codepath_t codepath;
  int32_t num_openmp_threads;

  int32_t unmuted;
  GList *iop;
  GList *iop_order_list;
  GList *iop_order_rules;
  GList *capabilities;
  JsonParser *noiseprofile_parser;
  struct dt_conf_t *conf;
  struct dt_develop_t *develop;
  struct dt_lib_t *lib;
  struct dt_view_manager_t *view_manager;
  struct dt_control_t *control;
  struct dt_control_signal_t *signals;
  struct dt_gui_gtk_t *gui;
  struct dt_mipmap_cache_t *mipmap_cache;
  struct dt_image_cache_t *image_cache;
  struct dt_bauhaus_t *bauhaus;
  const struct dt_database_t *db;
  const struct dt_pwstorage_t *pwstorage;
  const struct dt_camctl_t *camctl;
  const struct dt_collection_t *collection;
  struct dt_selection_t *selection;
  struct dt_points_t *points;
  struct dt_imageio_t *imageio;
  struct dt_opencl_t *opencl;
  struct dt_dbus_t *dbus;
  struct dt_undo_t *undo;
  struct dt_colorspaces_t *color_profiles;
  struct dt_l10n_t *l10n;
  dt_pthread_mutex_t db_image[DT_IMAGE_DBLOCKS];
  dt_pthread_mutex_t dev_threadsafe;
  dt_pthread_mutex_t plugin_threadsafe;
  dt_pthread_mutex_t capabilities_threadsafe;
  dt_pthread_mutex_t exiv2_threadsafe;
  dt_pthread_mutex_t readFile_mutex;
  char *progname;
  char *datadir;
  char *sharedir;
  char *plugindir;
  char *localedir;
  char *tmpdir;
  char *configdir;
  char *cachedir;
  dt_lua_state_t lua_state;
  GList *guides;
  double start_wtime;
  GList *themes;
  int32_t unmuted_signal_dbg_acts;
  gboolean unmuted_signal_dbg[DT_SIGNAL_COUNT];
  GTimeZone *utc_tz;
  GDateTime *origin_gdt;
  struct dt_sys_resources_t dtresources;
} darktable_t;

typedef struct
{
  double clock;
  double user;
} dt_times_t;

extern darktable_t darktable;

int dt_init(int argc, char *argv[], const gboolean init_gui, const gboolean load_data, lua_State *L);
void dt_get_sysresource_level();
void dt_cleanup();
void dt_print(dt_debug_thread_t thread, const char *msg, ...) __attribute__((format(printf, 2, 3)));
/* same as above but without time stamp : nts = no time stamp */
void dt_print_nts(dt_debug_thread_t thread, const char *msg, ...) __attribute__((format(printf, 2, 3)));
/* same as above but requires additional DT_DEBUG_VERBOSE flag to be true */
void dt_vprint(dt_debug_thread_t thread, const char *msg, ...) __attribute__((format(printf, 2, 3)));
int dt_worker_threads();
size_t dt_get_available_mem();
size_t dt_get_singlebuffer_mem();

void *dt_alloc_align(size_t alignment, size_t size);
static inline void* dt_calloc_align(size_t alignment, size_t size)
{
  void *buf = dt_alloc_align(alignment, size);
  if(buf) memset(buf, 0, size);
  return buf;
}
static inline float *dt_alloc_align_float(size_t pixels)
{
  return (float*)__builtin_assume_aligned(dt_alloc_align(64, pixels * sizeof(float)), 64);
}
static inline float *dt_calloc_align_float(size_t pixels)
{
  float *const buf = (float*)dt_alloc_align(64, pixels * sizeof(float));
  if(buf) memset(buf, 0, pixels * sizeof(float));
  return (float*)__builtin_assume_aligned(buf, 64);
}
size_t dt_round_size(const size_t size, const size_t alignment);
size_t dt_round_size_sse(const size_t size);

#ifdef _WIN32
void dt_free_align(void *mem);
#define dt_free_align_ptr dt_free_align
#elif _DEBUG // debug build makes sure that we get a crash on using plain free() on an aligned allocation
void dt_free_align(void *mem);
#define dt_free_align_ptr dt_free_align
#else
#define dt_free_align(A) free(A)
#define dt_free_align_ptr free
#endif

static inline void dt_lock_image(int32_t imgid) ACQUIRE(darktable.db_image[imgid & (DT_IMAGE_DBLOCKS-1)])
{
  dt_pthread_mutex_lock(&(darktable.db_image[imgid & (DT_IMAGE_DBLOCKS-1)]));
}

static inline void dt_unlock_image(int32_t imgid) RELEASE(darktable.db_image[imgid & (DT_IMAGE_DBLOCKS-1)])
{
  dt_pthread_mutex_unlock(&(darktable.db_image[imgid & (DT_IMAGE_DBLOCKS-1)]));
}

static inline void dt_lock_image_pair(int32_t imgid1, int32_t imgid2) ACQUIRE(darktable.db_image[imgid1 & (DT_IMAGE_DBLOCKS-1)], darktable.db_image[imgid2 & (DT_IMAGE_DBLOCKS-1)])
{
  if(imgid1 < imgid2)
  {
    dt_pthread_mutex_lock(&(darktable.db_image[imgid1 & (DT_IMAGE_DBLOCKS-1)]));
    dt_pthread_mutex_lock(&(darktable.db_image[imgid2 & (DT_IMAGE_DBLOCKS-1)]));
  }
  else
  {
    dt_pthread_mutex_lock(&(darktable.db_image[imgid2 & (DT_IMAGE_DBLOCKS-1)]));
    dt_pthread_mutex_lock(&(darktable.db_image[imgid1 & (DT_IMAGE_DBLOCKS-1)]));
  }
}

static inline void dt_unlock_image_pair(int32_t imgid1, int32_t imgid2) RELEASE(darktable.db_image[imgid1 & (DT_IMAGE_DBLOCKS-1)], darktable.db_image[imgid2 & (DT_IMAGE_DBLOCKS-1)])
{
  dt_pthread_mutex_unlock(&(darktable.db_image[imgid1 & (DT_IMAGE_DBLOCKS-1)]));
  dt_pthread_mutex_unlock(&(darktable.db_image[imgid2 & (DT_IMAGE_DBLOCKS-1)]));
}

extern GdkModifierType dt_modifier_shortcuts;

// check whether the specified mask of modifier keys exactly matches, among the set Shift+Control+(Alt/Meta).
// ignores the state of any other shifting keys
static inline gboolean dt_modifier_is(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
{
  const GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask();
//TODO: on Macs, remap the GDK_CONTROL_MASK bit in desired_modifier_mask to be the bit for the Cmd key
  return ((state | dt_modifier_shortcuts) & modifiers) == desired_modifier_mask;
}

// check whether the given modifier state includes AT LEAST the specified mask of modifier keys
static inline gboolean dt_modifiers_include(const GdkModifierType state, const GdkModifierType desired_modifier_mask)
{
//TODO: on Macs, remap the GDK_CONTROL_MASK bit in desired_modifier_mask to be the bit for the Cmd key
  const GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask();
  // check whether all modifier bits of interest are turned on
  return ((state | dt_modifier_shortcuts) & (modifiers & desired_modifier_mask)) == desired_modifier_mask;
}


static inline gboolean dt_is_aligned(const void *pointer, size_t byte_count)
{
    return (uintptr_t)pointer % byte_count == 0;
}

static inline void * dt_alloc_sse_ps(size_t pixels)
{
  return __builtin_assume_aligned(dt_alloc_align(64, pixels * sizeof(float)), 64);
}

static inline void * dt_check_sse_aligned(void * pointer)
{
  if(dt_is_aligned(pointer, 64))
    return __builtin_assume_aligned(pointer, 64);
  else
    return NULL;
}

int dt_capabilities_check(char *capability);
void dt_capabilities_add(char *capability);
void dt_capabilities_remove(char *capability);
void dt_capabilities_cleanup();

static inline double dt_get_wtime(void)
{
  struct timeval time;
  gettimeofday(&time, NULL);
  return time.tv_sec - 1290608000 + (1.0 / 1000000.0) * time.tv_usec;
}

static inline void dt_get_times(dt_times_t *t)
{
  struct rusage ru;

  getrusage(RUSAGE_SELF, &ru);
  t->clock = dt_get_wtime();
  t->user = ru.ru_utime.tv_sec + ru.ru_utime.tv_usec * (1.0 / 1000000.0);
}

void dt_show_times(const dt_times_t *start, const char *prefix);

void dt_show_times_f(const dt_times_t *start, const char *prefix, const char *suffix, ...) __attribute__((format(printf, 3, 4)));

/** \brief check if file is a supported image */
gboolean dt_supported_image(const gchar *filename);

static inline size_t dt_get_num_threads()
{
#ifdef _OPENMP
  // we can safely assume omp_get_num_procs is > 0
  return (size_t)omp_get_num_procs();
#else
  return 1;
#endif
}

static inline int dt_get_thread_num()
{
#ifdef _OPENMP
  return omp_get_thread_num();
#else
  return 0;
#endif
}

// Allocate a buffer for 'n' objects each of size 'objsize' bytes for each of the program's threads.
// Ensures that there is no false sharing among threads by aligning and rounding up the allocation to
// a multiple of the cache line size.  Returns a pointer to the allocated pool and the adjusted number
// of objects in each thread's buffer.  Use dt_get_perthread or dt_get_bythread (see below) to access
// a specific thread's buffer.
static inline void *dt_alloc_perthread(const size_t n, const size_t objsize, size_t* padded_size)
{
  const size_t alloc_size = n * objsize;
  const size_t cache_lines = (alloc_size+63)/64;
  *padded_size = 64 * cache_lines / objsize;
  return __builtin_assume_aligned(dt_alloc_align(64, 64 * cache_lines * dt_get_num_threads()), 64);
}
static inline void *dt_calloc_perthread(const size_t n, const size_t objsize, size_t* padded_size)
{
  void *const buf = (float*)dt_alloc_perthread(n, objsize, padded_size);
  memset(buf, 0, *padded_size * dt_get_num_threads() * objsize);
  return buf;
}
// Same as dt_alloc_perthread, but the object is a float.
static inline float *dt_alloc_perthread_float(const size_t n, size_t* padded_size)
{
  return (float*)dt_alloc_perthread(n, sizeof(float), padded_size);
}
// Allocate floats, cleared to zero
static inline float *dt_calloc_perthread_float(const size_t n, size_t* padded_size)
{
  float *const buf = (float*)dt_alloc_perthread(n, sizeof(float), padded_size);
  if (buf)
  {
    for (size_t i = 0; i < *padded_size * dt_get_num_threads(); i++)
      buf[i] = 0.0f;
  }
  return buf;
}

// Given the buffer and object count returned by dt_alloc_perthread, return the current thread's private buffer.
#define dt_get_perthread(buf, padsize) DT_IS_ALIGNED((buf) + ((padsize) * dt_get_thread_num()))
// Given the buffer and object count returned by dt_alloc_perthread and a thread count in 0..dt_get_num_threads(),
// return a pointer to the indicated thread's private buffer.
#define dt_get_bythread(buf, padsize, tnum) DT_IS_ALIGNED((buf) + ((padsize) * (tnum)))

// Most code in dt assumes that the compiler is capable of auto-vectorization.  In some cases, this will yield
// suboptimal code if the compiler in fact does NOT auto-vectorize.  Uncomment the following line for such a
// compiler.
//#define DT_NO_VECTORIZATION

// For some combinations of compiler and architecture, the compiler may actually emit inferior code if given
// a hint to vectorize a loop.  Uncomment the following line if such a combination is the compilation target.
//#define DT_NO_SIMD_HINTS

// copy the RGB channels of a pixel using nontemporal stores if possible; includes the 'alpha' channel as well
// if faster due to vectorization, but subsequent code should ignore the value of the alpha unless explicitly
// set afterwards (since it might not have been copied).  NOTE: nontemporal stores will actually be *slower*
// if we immediately access the pixel again.  This function should only be used when processing an entire
// image before doing anything else with the destination buffer.
static inline void copy_pixel_nontemporal(float *const __restrict__ out, const float *const __restrict__ in)
{
#if (__clang__+0 > 7) && (__clang__+0 < 10)
  for_each_channel(k,aligned(in,out:16)) __builtin_nontemporal_store(in[k],out[k]);
#else
  for_each_channel(k,aligned(in,out:16) dt_omp_nontemporal(out)) out[k] = in[k];
#endif
}

// copy the RGB channels of a pixel; includes the 'alpha' channel as well if faster due to vectorization, but
// subsequent code should ignore the value of the alpha unless explicitly set afterwards (since it might not have
// been copied)
static inline void copy_pixel(float *const __restrict__ out, const float *const __restrict__ in)
{
  for_each_channel(k,aligned(in,out:16)) out[k] = in[k];
}

// a few macros and helper functions to speed up certain frequently-used GLib operations
#define g_list_is_singleton(list) ((list) && (!(list)->next))
static inline gboolean g_list_shorter_than(const GList *list, unsigned len)
{
  // instead of scanning the full list to compute its length and then comparing against the limit,
  // bail out as soon as the limit is reached.  Usage: g_list_shorter_than(l,4) instead of g_list_length(l)<4
  while (len-- > 0)
  {
    if (!list) return TRUE;
    list = g_list_next(list);
  }
  return FALSE;
}

// advance the list by one position, unless already at the final node
static inline GList *g_list_next_bounded(GList *list)
{
  return g_list_next(list) ? g_list_next(list) : list;
}

static inline const GList *g_list_next_wraparound(const GList *list, const GList *head)
{
  return g_list_next(list) ? g_list_next(list) : head;
}

static inline const GList *g_list_prev_wraparound(const GList *list)
{
  // return the prior element of the list, unless already on the first element; in that case, return the last
  // element of the list.
  return g_list_previous(list) ? g_list_previous(list) : g_list_last((GList*)list);
}

void dt_print_mem_usage();

void dt_configure_runtime_performance(const int version, char *config_info);
// helper function which loads whatever image_to_load points to: single image files or whole directories
// it tells you if it was a single image or a directory in single_image (when it's not NULL)
int dt_load_from_string(const gchar *image_to_load, gboolean open_image_in_dr, gboolean *single_image);

#define dt_unreachable_codepath_with_desc(D)                                                                 \
  dt_unreachable_codepath_with_caller(D, __FILE__, __LINE__, __FUNCTION__)
#define dt_unreachable_codepath() dt_unreachable_codepath_with_caller("unreachable", __FILE__, __LINE__, __FUNCTION__)
static inline void dt_unreachable_codepath_with_caller(const char *description, const char *file,
                                                       const int line, const char *function)
{
  fprintf(stderr, "[dt_unreachable_codepath] {%s} %s:%d (%s) - we should not be here. please report this to "
                  "the developers.",
          description, file, line, function);
  __builtin_unreachable();
}

/** define for max path/filename length */
#define DT_MAX_FILENAME_LEN 256

#ifndef PATH_MAX
/*
 * from /usr/include/linux/limits.h (Linux 3.16.5)
 * Some systems might not define it (e.g. Hurd)
 *
 * We do NOT depend on any specific value of this env variable.
 * If you want constant value across all systems, use DT_MAX_PATH_FOR_PARAMS!
 */
#define PATH_MAX 4096
#endif

/*
 * ONLY TO BE USED FOR PARAMS!!! (e.g. dt_imageio_disk_t)
 *
 * WARNING: this should *NEVER* be changed, as it will break params,
 *          created with previous DT_MAX_PATH_FOR_PARAMS.
 */
#define DT_MAX_PATH_FOR_PARAMS 4096

/*
 * Helper functions for transition to gnome style xgettext translation context marking
 *
 * Many calls expect untranslated strings because they need to store them as ids in a language independent way.
 * They then internally before displaying call Q_ to translate, which allows an embedded translation context to be specified.
 * The qnome format "context|string" is used.
 * Intltool does not support this format when it scans N_, so NC_("context","string") has to be used.
 * But the standard NC_ does not propagate the context with the string. So here it is overridden to combine both parts.
 *
 * A better solution would be to switch to a modern xgettext https://wiki.gnome.org/MigratingFromIntltoolToGettext
 *
 *    xgettext --keyword=Q_:1g --keyword=N_:1g would allow using standard N_("context|string") to mark and pass on unchanged.
 *
 * This would also enable contextualised strings in introspection markups, like
 *
 *    DT_INTENT_SATURATION = INTENT_SATURATION, // $DESCRIPTION: "rendering intent|saturation"
 *
 * Before storing in a language-indpendent format, like shortcutsrc, NQ_ should be used to strip any context from the string.
 */
#undef NC_
#define NC_(Context, String) (Context "|" String)

static inline const gchar *NQ_(const gchar *String)
{
  const gchar *context_end = strchr(String, '|');
  return context_end ? context_end + 1 : String;
}

// clang-format off
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
// clang-format on
