#include "EXTERN.h"
#include "perl.h"
#include "pdl.h"
#include "pdlcore.h"

#define PDL PDL_LinearAlgebra_Complex
extern Core *PDL;

#define PDL_LA_COMPLEX_INIT_PUSH(pdlvar, type, valp, svpdl) \
   pdl *pdlvar = PDL->pdlnew(); \
   PDL->setdims(pdlvar, dims, ndims); \
   pdlvar->datatype = type + type_add; \
   pdlvar->data = valp; \
   pdlvar->state |= PDL_DONTTOUCHDATA | PDL_ALLOCATED; \
   ENTER;   SAVETMPS;   PUSHMARK(sp); \
    SV *svpdl = sv_newmortal(); \
    PDL->SetSV_PDL(svpdl, pdlvar); \
    svpdl = sv_bless(svpdl, bless_stash); \
    XPUSHs(svpdl); \
   PUTBACK;

#define PDL_LA_COMPLEX_UNINIT(pdl) \
   PDL->setdims(pdl, odims, sizeof(odims)/sizeof(odims[0])); \
   pdl->state &= ~ (PDL_ALLOCATED |PDL_DONTTOUCHDATA); \
   pdl->data=NULL;

/* replace BLAS one so don't terminate on bad input */
int xerbla_(char *sub, int *info) { return 0; }

#define SEL_FUNC2(letter, letter2, type, pdl_type, args, init, uninit) \
  static SV* letter ## letter2 ## select_func = NULL; \
  void letter ## letter2 ## select_func_set(SV* func) { \
    if (letter ## letter2 ## select_func) SvREFCNT_dec(letter ## letter2 ## select_func); \
    SvREFCNT_inc(letter ## letter2 ## select_func = func); \
  } \
  PDL_Long letter ## letter2 ## select_wrapper args \
  { \
    dSP; \
    PDL_Indx odims[] = {0}; \
    PDL_Indx *dims = NULL; \
    PDL_Indx ndims = 0; \
    int type_add = PDL_CF - PDL_F; \
    HV *bless_stash = gv_stashpv("PDL", 0); \
    init \
    int count = perl_call_sv(letter ## select_func, G_SCALAR); \
    SPAGAIN; \
    uninit \
    if (count !=1) croak("Error calling perl function\n"); \
    long ret = (long ) POPl ; \
    PUTBACK ;   FREETMPS ;   LEAVE ; \
    return ret; \
  }

#define SEL_FUNC(letter, type, pdl_type) \
  SEL_FUNC2(letter, , type, pdl_type, (type *p), \
    PDL_LA_COMPLEX_INIT_PUSH(pdl, pdl_type, p, svpdl), \
    PDL_LA_COMPLEX_UNINIT(pdl) \
  )
SEL_FUNC(f, float, PDL_F)
SEL_FUNC(d, double, PDL_D)

#define GSEL_FUNC(letter, type, pdl_type) \
  SEL_FUNC2(letter, g, type, pdl_type, (type *p, type *q), \
    PDL_LA_COMPLEX_INIT_PUSH(pdl1, pdl_type, p, svpdl1) \
    PDL_LA_COMPLEX_INIT_PUSH(pdl2, pdl_type, q, svpdl2), \
    PDL_LA_COMPLEX_UNINIT(pdl1) \
    PDL_LA_COMPLEX_UNINIT(pdl2) \
  )
GSEL_FUNC(f, float, PDL_F)
GSEL_FUNC(d, double, PDL_D)

#define TRACE(letter, type) \
  void c ## letter ## trace(int n, void *a1, void *a2) { \
    type *mat = a1, *res = a2; \
    PDL_Indx i; \
    res[0] = mat[0]; \
    res[1] = mat[1]; \
    for (i = 1; i < n; i++) \
    { \
          res[0] += mat[(i*(n+1))*2]; \
          res[1] += mat[(i*(n+1))*2+1]; \
    } \
  }
TRACE(f, float)
TRACE(d, double)