#ifndef __PDLCORE_H
#define __PDLCORE_H

/* version 20: memory-management changes */
/* on 21, look for comments "CORE21", unify pdl_trans per_pdl_flags, par_flags; remove threadloop #defines; change creating to char; relocate struct pdl.value appropriately, remove pdl_null, safe_indterm, initbroadcaststruct to take trans & remove in[cd]_sizes, remove pdl_{read,writeback}data_affine */
#define PDL_CORE_VERSION 20
#define startbroadcastloop startthreadloop
#define pdl_startbroadcastloop pdl_startthreadloop
#define iterbroadcastloop iterthreadloop
#define pdl_iterbroadcastloop pdl_iterthreadloop
#define get_broadcastdims get_threaddims

#include "EXTERN.h"   /* std perl include */
#include "perl.h"     /* std perl include */
#include "XSUB.h"  /* for the win32 perlCAPI crap */
#include "ppport.h"  /* include this AFTER XSUB.h */

#include <stdint.h>

#if defined(CONTEXT) && defined(__osf__)
#undef CONTEXT
#endif

#ifdef PDL_IN_CORE
#define PDL_CORE_(func) pdl_##func
#define PDL_TYPENAME(t) (!PDL.type_names ? "ERROR: type_names not set" : (t < 0 || t >= PDL_NTYPES) ? "INVALID" : PDL.type_names[t])
#else
#define PDL_CORE_(func) PDL->func
#define PDL_TYPENAME(t) (!PDL->type_names ? "ERROR: type_names not set" : (t < 0 || t >= PDL_NTYPES) ? "INVALID" : PDL->type_names[t])
#endif

#define PDL_RECURSE_CHECK(var) \
  if (var > 1000) \
    return pdl_make_error_simple(PDL_EUSERERROR, "PDL:Internal Error: data structure recursion limit exceeded (max 1000 levels)\n\tThis could mean that you have found an infinite-recursion error in PDL, or\n\tthat you are building data structures with very long dataflow dependency\n\tchains.  You may want to try using sever() to break the dependency")

#include "pdl.h"
/* the next one causes trouble in c++ compiles - exclude for now */
#ifndef __cplusplus
#include "pdlmagic.h"
#endif

#define SVavref(x) (SvROK(x) && SvTYPE(SvRV(x))==SVt_PVAV)

/*  Use our own barf and our own warn.
 *  We defer barf (and warn) handling until after multi-threaded (i.e pthreading)
 *  processing is finished.
 *  This is needed because segfaults happen when perl's croak is called
 *  during one of the spawned pthreads for PDL processing.
 */
#define barf PDL_CORE_(pdl_barf)
#undef warn
#define warn PDL_CORE_(pdl_warn)

PDL_Indx av_ndcheck(AV* av, AV* dims, int level, int *datalevel);
pdl* pdl_from_array(AV* av, AV* dims, pdl_datatypes type, pdl* p);
pdl_error pdl_writebackdata_vaffine(pdl *it);
pdl_error pdl_readdata_vaffine(pdl *it);
pdl_error pdl_dim_checks(pdl_transvtable *vtable, pdl **pdls,
  pdl_broadcast *broadcast, PDL_Indx nimpl, PDL_Indx *creating,
  PDL_Indx *ind_sizes, char load_only);
PDL_Indx pdl_get_offset(PDL_Indx* pos, PDL_Indx* dims, PDL_Indx *incs, PDL_Indx offset, PDL_Indx ndims);
pdl_error pdl_propagate_badflag_dir(pdl *it, int newval, char is_fwd, int recurse_count);

/* pdlutil.c */
typedef enum {
  PDL_FLAGS_TRANS,
  PDL_FLAGS_PDL,
  PDL_FLAGS_VTABLE
} pdl_flags;
pdl_error pdl_param_error(
  pdl_transvtable *vtable, int paramIndex,
  pdl **pdls, PDL_Indx nimpl, PDL_Indx *creating,
  char *pat, ...
);
void pdl_print_iarr(PDL_Indx *iarr, int n);
void pdl_dump_transvtable(pdl_transvtable *vtable, int nspac);
void pdl_dump_broadcast(pdl_broadcast *broadcast);
void pdl_dump_broadcasting_info(
  int npdls, PDL_Indx* creating, int target_pthread,
  PDL_Indx *nbroadcastedDims, PDL_Indx **broadcastedDims, PDL_Indx **broadcastedDimSizes,
  int maxPthreadPDL, int maxPthreadDim, int maxPthread
);
void pdl_dump_flags_fixspace(int flags, int nspac, pdl_flags type);
void pdl_dump_trans_fixspace(pdl_trans *it, int nspac);
void pdl_dump_anyval(PDL_Anyval v);

#define PDL_CORE_LIST(X) \
  X(SvPDLV, pdl*, ( SV* )) \
  X(SetSV_PDL, void, ( SV *sv, pdl *it )) \
  X(pdlnew, pdl*, (void)) \
  X(destroy, pdl_error, (pdl *it)) \
  X(null, pdl*, (void)) \
  X(scalar, pdl*, (PDL_Anyval anyval)) \
  X(hard_copy, pdl*, ( pdl* )) \
  X(converttype, pdl_error, ( pdl*, pdl_datatypes )) \
  X(smalloc, void*, ( STRLEN )) \
  X(howbig, size_t, ( pdl_datatypes )) \
  X(packdims, PDL_Indx*, ( SV* sv, PDL_Indx *ndims )) \
  X(setdims, pdl_error, ( pdl* it, PDL_Indx* dims, PDL_Indx ndims )) \
  X(at0, PDL_Anyval, ( pdl* x )) /*CORE21*/ \
  X(reallocdims, pdl_error, ( pdl *it,PDL_Indx ndims )) \
  X(reallocbroadcastids, pdl_error, ( pdl *it,PDL_Indx ndims )) \
  X(resize_defaultincs, void, ( pdl *it )) /* Make incs out of dims */ \
  X(clearbroadcaststruct, void, (pdl_broadcast *it)) \
  X(initbroadcaststruct, pdl_error, (int nobl,pdl **pdls,PDL_Indx *realdims, \
    PDL_Indx *creating,PDL_Indx npdls,pdl_transvtable *transvtable, \
    pdl_broadcast *broadcast,PDL_Indx *ind_sizes,PDL_Indx *inc_sizes, \
    char *flags, int noPthreadFlag)) \
  X(redodims_default, pdl_error, (pdl_trans *)) \
  X(startbroadcastloop, int, (pdl_broadcast *broadcast,pdl_error (*func)(pdl_trans *), \
    pdl_trans *, pdl_error *)) \
  X(get_threadoffsp, PDL_Indx*, (pdl_broadcast *broadcast)) /* For pthreading */ \
  X(get_broadcastdims, PDL_Indx*, (pdl_broadcast *broadcast)) /* For pthreading */ \
  X(iterbroadcastloop, int, (pdl_broadcast *broadcast, PDL_Indx which)) \
  X(freebroadcaststruct, void, (pdl_broadcast *broadcast)) \
  X(broadcast_create_parameter, pdl_error, (pdl_broadcast *broadcast,PDL_Indx j, \
    PDL_Indx *dims, int temp)) \
  X(add_deletedata_magic, pdl_error,  (pdl *it,void (*func)(pdl *, Size_t param), \
    Size_t param)) /* Automagic destructor */ \
  X(setdims_careful, pdl_error, (pdl *pdl)) \
  X(get_offs, PDL_Anyval, (pdl *pdl,PDL_Indx offs)) /*CORE21*/ \
  X(set, pdl_error, ( void* x, pdl_datatypes datatype, PDL_Indx* pos, PDL_Indx* dims, \
    PDL_Indx *incs, PDL_Indx offs, PDL_Indx ndims, PDL_Anyval value)) \
  X(create_trans, pdl_trans *, (pdl_transvtable *vtable)) \
  X(type_coerce, pdl_error, (pdl_trans *trans)) \
  X(trans_badflag_from_inputs, char, (pdl_trans *trans)) \
  X(get_convertedpdl, pdl *, (pdl *pdl, pdl_datatypes type)) \
  X(make_trans_mutual, pdl_error, (pdl_trans *trans)) \
  X(make_physical, pdl_error, (pdl *it)) \
  X(make_physdims, pdl_error, (pdl *it)) \
  X(pdl_barf, void, (const char* pat,...)) \
  X(pdl_warn, void, (const char* pat,...)) \
  X(make_physvaffine, pdl_error, (pdl *it)) \
  X(allocdata, pdl_error, (pdl *it)) \
  X(safe_indterm, PDL_Indx, (PDL_Indx dsz, PDL_Indx at, char *file, int lineno)) \
  X(propagate_badflag, void, (pdl *it, int newval)) \
  X(propagate_badvalue, void, (pdl *it)) \
  X(changed, pdl_error, (pdl *it, int what, int recursing)) \
  X(get_pdl_badvalue, PDL_Anyval, (pdl *it)) /*CORE21*/ \
  X(get_badvalue, PDL_Anyval, (pdl_datatypes datatype)) /*CORE21*/ \
  X(set_datatype, pdl_error, (pdl *a, pdl_datatypes datatype)) \
  X(hdr_copy, SV *, (SV *hdrp)) \
  X(hdr_childcopy, void, (pdl_trans *trans)) \
  X(readdata_affine, pdl_error, (pdl_trans *trans)) \
  X(writebackdata_affine, pdl_error, (pdl_trans *trans)) \
  X(affine_new, pdl_error, (pdl *par,pdl *child,PDL_Indx offs,PDL_Indx *dims,PDL_Indx ndims,PDL_Indx *incs,PDL_Indx nincs)) \
  X(converttypei_new, pdl_error, (pdl *par,pdl *child,pdl_datatypes type)) \
  X(dump, void, (pdl *it)) \
  X(sever, pdl_error, (pdl *a)) \
  X(slice_args_parse_sv, pdl_slice_args*, ( SV* )) \
  X(online_cpus, int, (void)) \
  X(magic_get_thread, int, (pdl *)) \
  X(pdl_seed, uint64_t, (void)) \
  X(trans_check_pdls, pdl_error, (pdl_trans *trans)) \
  X(make_error, pdl_error, (pdl_error_type e, const char *fmt, ...)) \
  X(make_error_simple, pdl_error, (pdl_error_type e, const char *msg)) \
  X(barf_if_error, void, (pdl_error err)) \
  X(error_accumulate, pdl_error, (pdl_error err_current, pdl_error err_new)) \
  X(packpdls, pdl **, ( SV* sv, PDL_Indx *npdls )) \
  X(unpackpdls, SV*, ( pdl **, PDL_Indx npdls )) \
  X(packstrings, char **, ( SV* sv, PDL_Indx *nstrings )) \
  X(prealloc_trans_children, pdl_error, (pdl *it, PDL_Indx howmany)) \

/*************** Function prototypes *********************/
#define X(sym, rettype, args) \
  rettype pdl_ ## sym args;
PDL_CORE_LIST(X)
#undef X

#define X(symbol, ctype, ppsym, ...) \
PDL_Indx pdl_setav_ ## ppsym(ctype * pdata, AV* av, \
  PDL_Indx* pdims, PDL_Indx ndims, PDL_Indx level, ctype undefval, pdl *p);
PDL_TYPELIST_ALL(X)
#undef X

/* Structure to hold pointers core PDL routines so as to be used by many modules */

#if defined(PDL_clean_namespace) || defined(PDL_OLD_API)
#error PDL_clean_namespace and PDL_OLD_API defines have been removed. Use PDL->pdlnew() instead of PDL->new().
#endif

struct Core {
  I32    Version;
  pdl_badvals bvals;  /* store the default bad values */

#define X(sym, rettype, args) \
  rettype (*sym) args;
  PDL_CORE_LIST(X)
#undef X

  char **type_names;
  pdl_datatypes ntypes;
};

typedef struct Core Core;

#define PDL_DECLARE_PARAMETER(type, name, pdlname, nullcheck, ppsym) \
  type *name ## _datap = ((type *)(PDL_REPRP(pdlname))); \
  if ((nullcheck) && pdlname->nvals > 0 && !name ## _datap) \
    return PDL_CORE_(make_error)(PDL_EUSERERROR, "parameter " #name "=%p got NULL data", pdlname); \

#define PDL_DECLARE_PARAMETER_BADVAL(type, name, pdlname, nullcheck, ppsym) \
  PDL_DECLARE_PARAMETER(type, name, pdlname, nullcheck, ppsym) \
  type name ## _badval = pdlname->has_badvalue ? pdlname->badvalue.value.ppsym : PDL->bvals.ppsym; (void)name ## _badval; \
  char name ## _badval_isnan = PDL_ISNAN_ ## ppsym(name ## _badval); (void) name ## _badval_isnan; \

/* __PDLCORE_H */
#endif