#include "lattices.h"
#include <limits.h>
#include <math.h>
#include <string.h>

#define MIN(x,y) ((x)<(y)?(x):(y))

// generic converting extractors

PGenPolReturnData PGen_next_pol_from_cart2(PGen *g) {
  const PGenCart2ReturnData c = PGen_next_cart2(g);
  if (c.flags & PGEN_DONE)
    return PGenPolDoneVal;
  else {
    PGenPolReturnData p;
    p.flags = (c.flags & ~QPMS_COORDS_BITRANGE) | QPMS_COORDS_POL;
    p.point_pol = cart2pol(c.point_cart2);
    return p;
  }
}

PGenCart2ReturnData PGen_next_cart2_from_pol(PGen *g) {
  const PGenPolReturnData p = PGen_next_pol(g);
  if (p.flags & PGEN_DONE)
    return PGenCart2DoneVal;
  else {
    PGenCart2ReturnData c;
    c.flags = (p.flags & ~QPMS_COORDS_BITRANGE) | QPMS_COORDS_CART2;
    c.point_cart2 = pol2cart(p.point_pol);
    return c;
  }
}

PGenSphReturnData PGen_next_sph_from_cart3(PGen *g) {
  const PGenCart3ReturnData c = PGen_next_cart3(g);
  if (c.flags & PGEN_DONE)
    return PGenSphDoneVal;
  else {
    PGenSphReturnData s;
    s.flags = (c.flags & ~QPMS_COORDS_BITRANGE) | QPMS_COORDS_SPH;
    s.point_sph = cart2sph(c.point_cart3);
    return s;
  }
}

PGenCart3ReturnData PGen_next_cart3_from_cart2xy(PGen *g) {
  const PGenCart2ReturnData c2 = PGen_next_cart2(g);
  if (c2.flags & PGEN_DONE)
    return PGenCart3DoneVal;
  else {
    PGenCart3ReturnData c3;
    c3.flags = (c2.flags & ~QPMS_COORDS_BITRANGE) | QPMS_COORDS_CART3;
    c3.point_cart3 = cart22cart3xy(c2.point_cart2);
    return c3;
  }
}

PGenSphReturnData PGen_next_sph_from_cart2(PGen *g) {
  const PGenCart2ReturnData c = PGen_next_cart2(g);
  if (c.flags & PGEN_DONE)
    return PGenSphDoneVal;
  else {
    PGenSphReturnData s;
    s.flags = (c.flags & ~QPMS_COORDS_BITRANGE) | QPMS_COORDS_SPH;
    s.point_sph = cart22sph(c.point_cart2);
    return s;
  }
}

PGenCart3ReturnData PGen_next_cart3_from_sph(PGen *g) {
  const PGenSphReturnData s = PGen_next_sph(g);
  if (s.flags & PGEN_DONE)
    return PGenCart3DoneVal;
  else {
    PGenCart3ReturnData c;
    c.flags = (s.flags & ~QPMS_COORDS_BITRANGE) | QPMS_COORDS_CART3;
    c.point_cart3 = sph2cart(s.point_sph);
    return c;
  }
}

// here, various "classes" of the PGenSph point generators are implemented.


// const PGenSphReturnData PGenSphDoneVal = {PGEN_DONE, {0,0,0}}; // defined already in lattices.h
// const PGenCart3ReturnData PGenCart3DoneVal = {PGEN_DONE, {0,0,0}}; // defined already in lattices.h

// General structure of a generator implementation looks like this:

#if 0
//==== PGen_NAME ====

extern const PGenClassInfo PGen_NAME; // forward declaration needed by constructor (may be placed in header file instead)

// Internal state structure
typedef struct PGen_NAME_StateData {
  ...
} PGen_NAME_StateData;

// Constructor
PGenSph PGen_NAME_new(...) {
  g->stateData = malloc(sizeof(PGen_NAME_StateData));
  ...
  PGenSph g = {&PGen_NAME, (void *) stateData};
  return g;
}

// Dectructor
void PGen_NAME_destructor(PGen *g) {
  ...
  free(g->stateData);
  g->stateData = NULL;
}

// Extractor, spherical coordinate output
PGenSphReturnData PGen_NAME_next_sph(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenSphDoneVal;
  else {
    PGen_NAME_StateData *s = (PGen_NAME_StateData *) g->stateData;
    if (... /* there are still points to be generated */) {
      ...
      PGenSphReturnData retval = {.../*flags*/, .../*thePoint*/};
      return retval;
    } else {
      PGen_destroy(g);
      return PGenSphDoneVal;
    }
  }
}

// Extractor, 3D cartesian coordinate output
PGenCart3ReturnData PGen_NAME_next_cart3(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenCart3DoneVal;
  else {
    PGen_NAME_StateData *s = (PGen_NAME_StateData *) g->stateData;
    if (... /* there are still points to be generated */) {
      ...
      PGenCart3ReturnData retval = {.../*flags*/, .../*thePoint*/};
      return retval;
    } else {
      PGen_destroy(g);
      return PGenCart3DoneVal;
    }
  }
}

// Class metadata structure; TODO maybe this can rather be done by macro.
const PGenClassInfo PGen_NAME = {
  "PGen_NAME",
  ?, //dimensionality
  PGEN_COORDS_????, // native coordinate system
  // some of the _next_... fun pointers can be NULL
  PGen_NAME_next,
  PGen_NAME_next_z,
  PGen_NAME_next_pol,
  PGen_NAME_next_sph,
  PGen_NAME_next_cart2,
  PGen_NAME_next_cart3,
  PGen_NAME_fetch,
  PGen_NAME_fetch_z,
  PGen_NAME_fetch_pol,
  PGen_NAME_fetch_sph,
  PGen_NAME_fetch_cart2,
  PGen_NAME_fetch_cart3,
  PGen_NAME_destructor
};

#endif // 0    


//==== PGenSph_FromPoint2DArray ====

// Internal state structure
typedef struct PGen_FromPoint2DArray_StateData {
  const point2d *base;
  size_t len;
  size_t currentIndex;
}PGen_FromPoint2DArray_StateData;

// Constructor
PGen PGen_FromPoint2DArray_new(const point2d *points, size_t len) {
  PGen_FromPoint2DArray_StateData *stateData = malloc(sizeof(PGen_FromPoint2DArray_StateData));
  stateData->base = points;
  stateData->len = len;
  stateData->currentIndex = 0;
  PGen g = {&PGen_FromPoint2DArray, (void *) stateData};
  return g;
}

// Destructor
void PGen_FromPoint2DArray_destructor(PGen *g) {
  free(g->stateData);
  g->stateData = NULL;
}

// Extractor, 2D cartesian (native)
PGenCart2ReturnData PGen_FromPoint2DArray_next_cart2(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenCart2DoneVal;
  else {
    PGen_FromPoint2DArray_StateData *s = (PGen_FromPoint2DArray_StateData *) g->stateData;
    if (s->currentIndex < s->len) {
      cart2_t thePoint = s->base[s->currentIndex];
      ++(s->currentIndex);
      PGenCart2ReturnData retval = {(PGEN_NOTDONE | PGEN_AT_XY | PGEN_COORDS_CART2), thePoint};
      return retval;
    } else {
      PGen_destroy(g);
      return PGenCart2DoneVal;
    }
  }
}

// Extractor, spherical
PGenSphReturnData PGen_FromPoint2DArray_next_sph(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenSphDoneVal;
  else {
    PGen_FromPoint2DArray_StateData *s = (PGen_FromPoint2DArray_StateData *) g->stateData;
    if (s->currentIndex < s->len) {
      sph_t thePoint = cart22sph(s->base[s->currentIndex]);
      ++(s->currentIndex);
      PGenSphReturnData retval = {(PGEN_AT_XY | PGEN_COORDS_SPH), thePoint};
      return retval;
    } else {
      PGen_destroy(g);
      return PGenSphDoneVal;
    }
  }
}

const PGenClassInfo PGen_FromPoint2DArray = {
  "PGen_FromPoint2DArray",
  2, // dimensionality
  PGEN_COORDS_CART2,
  NULL,//PGen_FromPoint2DArray_next,
  NULL,
  NULL,//PGen_FromPoint2DArray_next_pol,
  PGen_FromPoint2DArray_next_sph,
  PGen_FromPoint2DArray_next_cart2,
  NULL,//PGen_FromPoint2DArray_next_cart3,
  NULL,//PGen_FromPoint2DArray_fetch,
  NULL,//PGen_FromPoint2DArray_fetch_z,
  NULL,//PGen_FromPoint2DArray_fetch_pol,
  NULL,//PGen_FromPoint2DArray_fetch_sph,
  NULL,//PGen_FromPoint2DArray_fetch_cart2,
  NULL,//PGen_FromPoint2DArray_fetch_cart3,
  PGen_FromPoint2DArray_destructor,
};



//==== PGen_1D ====
//equidistant points along the z-axis;

extern const PGenClassInfo PGen_1D; // forward declaration needed by constructor (may be placed in header file instead)

/* // This had to go to the header file:
enum PGen_1D_incrementDirection{
    //PGEN1D_POSITIVE_INC, // not implemented
    //PGEN1D_NEGATIVE_INC, // not implemented
    PGEN1D_INC_FROM_ORIGIN,
    PGEN1D_INC_TOWARDS_ORIGIN
};
*/

// Internal state structure
typedef struct PGen_1D_StateData {
  long ptindex;
  //long stopindex;
  double minR, maxR;
  bool inc_minR, inc_maxR;
  double a; // lattice period
  double offset; // offset of the zeroth lattice point from origin (will be normalised to interval [-a/2,a/2]
  enum PGen_1D_incrementDirection incdir;
  //bool skip_origin;
} PGen_1D_StateData;

static inline long ptindex_inc(long i) {
  if (i > 0)
    return -i;
  else
    return -i + 1;
}

static inline long ptindex_dec(long i) {
  if (i > 0)
    return -i + 1;
  else
    return -i;
}

// Constructor, specified by maximum and maximum absolute value
PGen PGen_1D_new_minMaxR(double period, double offset, double minR, bool inc_minR, double maxR, bool inc_maxR, 
    PGen_1D_incrementDirection incdir) {
  PGen_1D_StateData *s = malloc(sizeof(PGen_1D_StateData));
  s->minR = minR;
  s->maxR = maxR;
  s->inc_minR = inc_minR;
  s->inc_maxR = inc_maxR;
  s->incdir = incdir;
  period = fabs(period);
  double offset_normalised = offset - period * floor(offset / period); // shift to interval [0, period]
  if (offset_normalised > period / 2) offset_normalised -= period; // and to interval [-period/2, period/2]
  s->offset = offset_normalised;
  if (offset_normalised > 0) // reverse the direction so that the conditions in _next() are hit in correct order
        period *= -1;  
  switch(s->incdir) {
    double curR;
    case PGEN_1D_INC_FROM_ORIGIN:
      s->ptindex = floor(minR / fabs(period));
      while ( (curR = fabs(s->offset + s->ptindex * period)) < minR || (!inc_minR && curR <= minR))
        s->ptindex = ptindex_inc(s->ptindex);
      break;
    case PGEN_1D_INC_TOWARDS_ORIGIN:
      s->ptindex = - ceil(maxR / fabs(period));
      while ( (curR = fabs(s->offset + s->ptindex * period)) > maxR || (!inc_minR && curR >= maxR))
        s->ptindex = ptindex_dec(s->ptindex);
      break;
    default:
      QPMS_WTF;
  }
  s->a = period;
  
  PGen g = {&PGen_1D, (void *) s};
  return g;
}

// Dectructor
void PGen_1D_destructor(PGen *g) {
  free(g->stateData);
  g->stateData = NULL;
}

// Extractor 1D number
PGenZReturnData PGen_1D_next_z(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenZDoneVal;
  PGen_1D_StateData *s = (PGen_1D_StateData *) g->stateData;
  const double zval = s->ptindex * s->a + s->offset;
  const double r = fabs(zval);
  bool theEnd = false;
  switch (s->incdir) {
    case PGEN_1D_INC_FROM_ORIGIN:
      if (r < s->maxR || (s->inc_maxR && r == s->maxR)) 
        s->ptindex = ptindex_inc(s->ptindex);
      else theEnd = true;
      break; 
    case PGEN_1D_INC_TOWARDS_ORIGIN:
      if (r > s->minR || (s->inc_minR && r == s->minR)) {
        if (s->ptindex == 0) // handle "underflow"
          s->minR = INFINITY;
        else
          s->ptindex = ptindex_dec(s->ptindex);
      } else theEnd = true;
      break;
    default:
      QPMS_INVALID_ENUM(s->incdir);
  }
  if (!theEnd) {
      const PGenZReturnData retval = {PGEN_NOTDONE | PGEN_AT_Z, zval};
      return retval;
  } else {
      PGen_destroy(g);
      return PGenZDoneVal;
  }
}

// Extractor spherical coordinates // TODO remove/simplify
PGenSphReturnData PGen_1D_next_sph(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenSphDoneVal;
  PGen_1D_StateData *s = (PGen_1D_StateData *) g->stateData;
  const double zval = s->ptindex * s->a + s->offset;
  const double r = fabs(zval);
  bool theEnd = false;
  switch (s->incdir) {
    case PGEN_1D_INC_FROM_ORIGIN:
      if (r < s->maxR || (s->inc_maxR && r == s->maxR)) 
        s->ptindex = ptindex_inc(s->ptindex);
      else theEnd = true;
      break; 
    case PGEN_1D_INC_TOWARDS_ORIGIN:
      if (r > s->minR || (s->inc_minR && r == s->minR)) {
        if (s->ptindex == 0) // handle "underflow"
          s->minR = INFINITY;
        else
          s->ptindex = ptindex_dec(s->ptindex);
      } else theEnd = true;
      break;
    default:
        QPMS_INVALID_ENUM(s->incdir);
  }
  if (!theEnd) {
      const PGenSphReturnData retval = {PGEN_NOTDONE | PGEN_AT_Z | PGEN_COORDS_SPH,
        {r, zval >= 0 ? 0 : M_PI, 0}};
      return retval;
  } else {
      PGen_destroy(g);
      return PGenSphDoneVal;
  }
}

// Class metadata structure; TODO maybe this can rather be done by macro.
const PGenClassInfo PGen_1D = {
  "PGen_1D",
  1, // dimensionality
  PGEN_COORDS_CART1,
  NULL, //PGen_1D_next,
  PGen_1D_next_z,
  NULL,//PGen_1D_next_pol,
  PGen_1D_next_sph,
  NULL,//PGen_1D_next_cart2,
  NULL,//PGen_1D_next_cart3,
  NULL,//PGen_1D_fetch,
  NULL,//PGen_1D_fetch_z,
  NULL,//PGen_1D_fetch_pol,
  NULL,//PGen_1D_fetch_sph,
  NULL,//PGen_1D_fetch_cart2,
  NULL,//PGen_1D_fetch_cart3,
  PGen_1D_destructor
};

//==== PGen_xyWeb ====
// 2D lattice generator in the "spiderweb" style, generated in the "perimetre" order,
// not strictly ordered (or limited) by distance from origin.
// The minR and maxR here refer to the TODO WWHAT

extern const PGenClassInfo PGen_xyWeb; // forward declaration needed by constructor (may be placed in header file instead)

// Internal state structure
typedef struct PGen_xyWeb_StateData {
  long i, j;
  unsigned short phase; // 0 to 5
  long layer;
  long last_layer; // generation stops when layer > last_layer
  double layer_min_height; // this * layer is what minR and maxR are compared to
  double minR, maxR;
  bool inc_minR, inc_maxR;
  cart2_t b1, b2; // lattice vectors
  cart2_t offset; // offset of the zeroth lattice point from origin (TODO will be normalised to the WS cell)
  // TODO type rectangular vs. triangular
  LatticeFlags lf;
} PGen_xyWeb_StateData;

// Constructor
PGen PGen_xyWeb_new(cart2_t b1, cart2_t b2, double rtol, cart2_t offset, double minR, bool inc_minR, double maxR, bool inc_maxR) {
  PGen_xyWeb_StateData *s =  malloc(sizeof(PGen_xyWeb_StateData));
  s->minR = minR; s->maxR = maxR;
  s->inc_minR = inc_minR;
  s->inc_maxR = inc_maxR;
  l2d_reduceBasis(b1, b2, &(s->b1), &(s->b2));
  s->offset = offset; // TODO shorten into the WS cell ?
  s->lf = l2d_detectRightAngles(s->b1, s->b2, rtol);
  s->layer_min_height = l2d_hexWebInCircleRadius(s->b1, s->b2);
  s->layer = ceil(s->minR/s->layer_min_height);
  if(!inc_minR && (s->layer * s->layer_min_height)  <= minR)
    ++(s->layer); 
  s->i = s->layer; s->j = 0; s->phase = 0; // init indices
  s->last_layer = floor(s->maxR/s->layer_min_height);
  if(!inc_maxR && (s->last_layer * s->layer_min_height) >= maxR)
    --(s->last_layer);
  PGen g = {&PGen_xyWeb, (void *) s};
  return g;
}

// Destructor
void PGen_xyWeb_destructor(PGen *g) {
  free(g->stateData);
  g->stateData = NULL;
}

// Extractor (2D cartesian, native)
PGenCart2ReturnData PGen_xyWeb_next_cart2(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenCart2DoneVal;
  else {
    PGen_xyWeb_StateData * const s = (PGen_xyWeb_StateData *) g->stateData;
    assert(s->layer >= 0);
    if (s->layer <= s->last_layer) {
      const cart2_t thePoint = cart2_add(s->offset,
          cart2_add(cart2_scale(s->i, s->b1), cart2_scale(s->j, s->b2)));
      if(s->layer == 0) { // origin is unique, proceed with next layer
        ++s->layer;
        s->phase = 0;
        s->i = s->layer;
        s->j = 0;
      }
      else if(s->lf & ORTHOGONAL_01) {
        // rectangular or square lattice, four perimeters
        switch(s->phase) {
          case 0: // initial i = l, j = 0
            --s->i;
            ++s->j;
            if(s->i <= 0) ++s->phase;
            break;
          case 1: // initial i = 0, j = l
            --s->i;
            --s->j;
            if(s->j <= 0) ++s->phase;
            break;
          case 2: // initial i = -l, j = 0
            ++s->i;
            --s->j;
            if(s->i >= 0) ++s->phase;
            break;
          case 3: // initial i = 0, j = -l
            ++s->i;
            ++s->j;
            if(s->j >= 0) ++s->phase;
            break;
          default:
            QPMS_WTF;
        }
        if(s->phase == 4) { // phase overflow, start new layer
          ++s->layer;
          s->phase = 0;
          s->i = s->layer;
          s->j = 0;
        }
      } else { // non-rectangular lattice, six perimeters
        switch(s->phase) {
          case 0:
            --s->i;
            ++s->j;
            if(s->i <= 0) ++s->phase;
            break;
          case 1:
            --s->i;
            if(s->i + s->j <= 0) ++s->phase;
            break;
          case 2:
            --s->j;
            if(s->j <= 0) ++s->phase;
            break;
          case 3:
            ++s->i;
            --s->j;
            if(s->i >= 0) ++s->phase;
            break;
          case 4:
            ++s->i;
            if(s->i + s->j >= 0) ++s->phase;
            break;
          case 5:
            ++s->j;
            if(s->j >= 0) ++s->phase;
            break;
          default:
            QPMS_WTF;
        }
        if(s->phase == 6) { // phase overflow, start next layer
          ++s->layer;
          s->phase = 0;
          s->i = s->layer;
          s->j = 0;
        }
      }
      PGenCart2ReturnData retval = {(PGEN_NOTDONE | PGEN_AT_XY | PGEN_COORDS_CART2), thePoint};
      return retval;
    } else {
      PGen_destroy(g);
      return PGenCart2DoneVal;
    }
  }
}

// Class metadata structure; TODO maybe this can rather be done by macro.
const PGenClassInfo PGen_xyWeb = {
  "PGen_xyWeb",
  2,
  PGEN_COORDS_CART2,
  NULL,//PGen_xyWeb_next, // FIXME I should really implement this.
  NULL,//PGen_xyWeb_next_z,
  PGen_next_pol_from_cart2, //NULL,//PGen_xyWeb_next_pol,
  PGen_next_sph_from_cart2, //NULL,//PGen_xyWeb_next_sph,
  PGen_xyWeb_next_cart2, // native
  PGen_next_cart3_from_cart2xy, //NULL,//PGen_xyWeb_next_cart3,
  NULL,//PGen_xyWeb_fetch, // FIXME I should really implement this
  NULL,//PGen_xyWeb_fetch_z,
  NULL,//PGen_xyWeb_fetch_pol,
  NULL,//PGen_xyWeb_fetch_sph,
  NULL,//PGen_xyWeb_fetch_cart2,
  NULL,//PGen_xyWeb_fetch_cart3,
  PGen_xyWeb_destructor
};


size_t PGen_xyWeb_sizecap(cart2_t b1, cart2_t b2, double rtol, cart2_t offset,
    double minR, bool inc_minR, double maxR, bool inc_maxR) 
{
  l2d_reduceBasis(b1, b2, &b1, &b2);
  LatticeFlags lf = l2d_detectRightAngles(b1, b2, rtol);
  double layer_min_height = l2d_hexWebInCircleRadius(b1, b2);
  long layer = ceil(minR / layer_min_height);
  if(!inc_minR && (layer * layer_min_height)  <= minR)
    ++layer; 
  long last_layer = floor(maxR / layer_min_height);
  if(!inc_maxR && (last_layer * layer_min_height) >= maxR)
    --(last_layer);
  // TODO less crude estimate (this one should be safe, however)
  return ((lf & ORTHOGONAL_01) ? 4 : 6) * (last_layer - layer + 1);
}


//==== PGen_LatticeRadialHeap ====


extern const PGenClassInfo PGen_LatticeRadialHeap; // forward declaration needed by constructor (may be placed in header file instead)

// Internal state structure
typedef struct PGen_LatticeRadialHeap_StateData {
  int ldim; // 2 or 3, must 
  int sdim;
  int layer;
  // Minimal distance of a point from the origin in the last layer (if the top of the heap exceeds this, a new layer must be evaluated
  double layer_min_r;
  size_t heap_len;
  size_t heap_capacity;
  double *r_heap;
  int *coord_heap;
  double b[0]; // basis vectors and offset
  double offset_r;
  double minR, maxR;
  bool inc_minR, inc_maxR;
} PGen_LatticeRadialHeap_StateData;

static inline double nd2norm(const double a[], int d) {
  double n = 0;
  for(int i = 0; i < d; ++i) 
    n += a[i]*a[i];
  return sqrt(n);
}

// General Constructor
PGen_LatticeRadialHeap_StateData *PGen_LatticeRadialHeap_new(int ldim, int sdim, double bvectors[], 
    double offset[], double minR, double maxR,
    bool inc_minR, bool inc_maxR) {
  QPMS_UNTESTED;
  PGen_LatticeRadialHeap_StateData *s =
       malloc(sizeof(PGen_LatticeRadialHeap_StateData) + (ldim + 1) * sdim * sizeof(double));
  s->ldim = ldim;
  s->sdim = sdim;
  memcpy(s->b, bvectors, ldim * sdim * sizeof(double));
  if (offset) {
    memcpy(s->b + ldim * sdim, offset, sdim * sizeof(double));
    s->offset_r = nd2norm(s->b + ldim*sdim, sdim);
  } else { 
    for (size_t i = 0; i < sdim; ++i) 
      s->b[ldim*sdim + i] = 0;
    s->offset_r = 0;
  }
  s->heap_len = 0;
  s->heap_capacity = 1024;
  QPMS_CRASHING_MALLOC(s->r_heap, sizeof(*s->r_heap) * s->heap_capacity);
  QPMS_CRASHING_MALLOC(s->coord_heap, sizeof(*s->coord_heap) * ldim * s->heap_capacity);
  s->layer = -1;
  s->layer_min_r = -INFINITY;
  s->inc_minR = inc_minR; s->inc_maxR = inc_maxR;
  
  return s;
}

// 2D constructor
PGen PGen_LatticeRadialHeap2D_new(cart2_t b1, cart2_t b2, cart2_t offset,
    double minR, bool inc_minR, double maxR, bool inc_maxR) {
  double bvectors[4];
  double offset_a[2];
  cart2_to_double_array(&bvectors[0], b1);
  cart2_to_double_array(&bvectors[2], b2);
  cart2_to_double_array(offset_a, offset);
  PGen g = {.c = &PGen_LatticeRadialHeap2D,
    .stateData = PGen_LatticeRadialHeap_new(2, 2, bvectors, offset_a,
        minR, maxR, inc_minR, inc_maxR)};
  return g;
}

// 3D constructor
PGen PGen_LatticeRadialHeap3D_new(const cart3_t *b1, const cart3_t *b2, const cart3_t *b3,
                const cart3_t *offset, double minR, bool inc_minR, double maxR, bool inc_maxR) {
  double bvectors[9];
  double offset_a[3];
  int ldim = 0;
  if (b1) {cart3_to_double_array(&bvectors[3*ldim], *b1); ++ldim;}
  if (b2) {cart3_to_double_array(&bvectors[3*ldim], *b2); ++ldim;}
  if (b3) {cart3_to_double_array(&bvectors[3*ldim], *b3); ++ldim;}
  QPMS_ENSURE(ldim > 0, "At least one basis vector must be specified (non-NULL)");
  PGen g = {.c = &PGen_LatticeRadialHeap3D,
    .stateData = PGen_LatticeRadialHeap_new(ldim, 3, bvectors,
       offset ? (cart3_to_double_array(offset_a, *offset), offset_a) : NULL,
       minR, maxR, inc_minR, inc_maxR)};
  return g;
}

// Increment a counter array with constant sum; in the beginning, both counter[] and counter_cumsum[]
// are expected to be init'd to {thesum, 0, 0, ..., 0}
// Everything is expected to be positive (although the types are signed)
static inline _Bool counter_increment(const int ldim, int counter[], 
    int counter_cumsum[], const int thesum) {
  for(int i = 1; i < ldim; ++i) {
    if(counter_cumsum[i] < thesum) { // Can increment this, do it.
      ++counter[i];
      ++counter_cumsum[i];
      for(int j = i - 1; j > 0; --j) { // Zero the preceding ones
        counter[j] = 0;
        counter_cumsum[j] = counter_cumsum[i];
      }
      // Determine the last digit
      counter[0] = thesum - counter_cumsum[1];
      return 1; // Incremented successfully
    }
  }
  return 0; // Could not increment (counter exhausted)
}

// Assuming the counter is initialised to all non-negative values, step through 
// all the possible sign combinations. In the end, return them back to non-negative
// and return false.
static inline _Bool counter_signcycle(const int ldim, int counter[]) {
  for(int i = 0; i < ldim; ++i) { // Flip signs until a sign flipped to negative (cool, right?)
    counter[i] = -counter[i];
    if (counter[i] < 0)
      return 1;
  }
  return 0;
}

static inline double PGen_LatticeRadialHeap_nextlayer(PGen_LatticeRadialHeap_StateData *s) {
  double mindist = 0;
  s->layer++;
  double minr = +INFINITY;
  const int ldim = s->ldim;
  const int sdim = s->sdim;
  int *counter, *counter_cumsum, *tmp;
  QPMS_CRASHING_CALLOC(counter, s->ldim * 2,  sizeof(*counter));
  counter_cumsum = counter + s->ldim;
  counter[0] = s->layer;
  counter_cumsum[0] = s->layer;

  do {
    if (s->heap_len >= s->heap_capacity - 1) { // Check heap capacity
      s->heap_capacity = s->heap_capacity >= 1024 ? s->heap_capacity * 2 : 1024;
      QPMS_CRASHING_REALLOC(s->r_heap, sizeof(*s->r_heap) * s->heap_capacity);
      QPMS_CRASHING_REALLOC(s->coord_heap, sizeof(*s->coord_heap) * ldim * s->heap_capacity);
    }
    double r = 0;
    for(int i = 0; i < s->sdim; ++i) { // calculate r
      double component = s->b[ldim * sdim + i]; // offset
      for (int j = 0; j < s->ldim; ++j)
        component += s->b[j * sdim + i] * counter[j];
      r += component * component;
    }
    r = sqrt(r);
    minr = MIN(r, minr);
    // Add to the heaps
    int position = s->heap_len++;
    s->r_heap[position] = r;
    memcpy(&s->coord_heap[ldim * position], counter, sizeof(*s->coord_heap) * ldim);
    // bubble-up
    while(position > 0) {
      int parent = (position - 1) / 2;
      if  (s->r_heap[parent] > r)  { // swap
        s->r_heap[position] = s->r_heap[parent];
        memcpy(&s->coord_heap[ldim * position], &s->coord_heap[ldim * parent], sizeof(*s->coord_heap) * ldim);
        s->r_heap[parent] = r;
        memcpy(&s->coord_heap[ldim * parent], counter, sizeof(*s->coord_heap) * ldim);
        position = parent;
      } 
      else break;
    }
  } while (counter_signcycle(s->ldim, counter)
      || counter_increment(s->ldim, counter, counter_cumsum, s->layer));

  free(counter);
  return minr;
}



// sdim-independent generator method, returns the integer lattice coordinates
// N.B. This ALWAYS produces, not checking against maxR or destructing the generator itself
// (although it does discard the points with distance smaller (or equal) than minR)
// TODO maybe I want to return (double) r instead of (int) 0
int PGen_LatticeRadialHeap_fillNext_intcoords(PGen_LatticeRadialHeap_StateData *s, int target[]) {
  bool hit = false;
  while(!hit) {
    // Ensure that we have sufficiently filled heap
    while (s->heap_len < 1 || s->r_heap[0] + s->offset_r > s->layer_min_r) 
      s->layer_min_r = PGen_LatticeRadialHeap_nextlayer(s);
    double r = s->r_heap[0];
    hit = (r > s->minR || (s->inc_minR && r == s->minR));
    if (hit) memcpy(target, s->coord_heap, s->ldim * sizeof(*target));
    // Heap extract anyway
    // Move last element to root
    --(s->heap_len);
    s->r_heap[0] = s->r_heap[s->heap_len];
    memmove(s->coord_heap, &s->coord_heap[s->ldim * s->heap_len], s->ldim * sizeof(*s->coord_heap));
    // Bubble down
    int pos = 0;
    while(1) {
      int largest = pos, kidL = 2*pos+1, kidR = 2*pos+2;
      if (kidL < s->heap_len && s->r_heap[kidL] > s->r_heap[largest])
        largest = kidL;
      if (kidR < s->heap_len && s->r_heap[kidR] > s->r_heap[largest])
        largest = kidR;
      if (largest == pos) 
        break;
      else { // swap
        s->r_heap[pos] = s->r_heap[largest];
        s->r_heap[largest] = r;
        memcpy(&s->coord_heap[s->ldim * pos], &s->coord_heap[s->ldim * largest], s->ldim * sizeof(*s->coord_heap));
        memcpy(&s->coord_heap[s->ldim * largest], &s->coord_heap[s->ldim * s->heap_len /*it's still there*/],
            s->ldim * sizeof(*s->coord_heap));
      }
    }
  }
  return 0;
}

// sdim-independent generator method, fills the point's cartesian coordinates, including the offset
// N.B. This ALWAYS produces, returning 0 if maxR not exceeded, else -2 
// (it does discard the points with distance smaller (or equal) than minR)
#define LRH_STACKBUFSIZ 3 // Avoid heap allocation for typical dimensions
int PGen_LatticeRadialHeap_fillNext_cart(PGen *g, double target[]) {
  if (g->stateData == NULL) // already destroyed
    return -1; // LPTODO some other return value?
  else {
    PGen_LatticeRadialHeap_StateData * const s = g->stateData;
    int stackbuf[LRH_STACKBUFSIZ];
    int *buf;
    if (s->sdim > LRH_STACKBUFSIZ) {
      QPMS_CRASHING_MALLOC(buf, s->sdim * sizeof(*buf));
    } else
      buf = stackbuf;
    QPMS_ENSURE_SUCCESS(PGen_LatticeRadialHeap_fillNext_intcoords(s, buf));
    // Calculate the actual point
    double r = 0;
    for(int i = 0; i < s->sdim; ++i) { // calculate r
      double component = s->b[s->ldim * s->sdim + i]; // offset
      for (int j = 0; j < s->ldim; ++j)
        component += s->b[j * s->sdim + i] * buf[j];
      r += component * component;
      target[i] = component;
    }
    r = sqrt(r);
    if (s->sdim > LRH_STACKBUFSIZ) free(buf);

    if (r < s->maxR || (r == s->maxR && s->inc_maxR))
      return 0;
    else
      return -2;
  }
}

// Destructor
void PGen_LatticeRadialHeap_destructor(PGen *g) {
  PGen_LatticeRadialHeap_StateData *s = g->stateData;
  free(s->r_heap);
  free(s->coord_heap);
  free(g->stateData);
  g->stateData = NULL;
}

// Extractor, 2D cartesian coordinate output
PGenCart2ReturnData PGen_LatticeRadialHeap2D_next_cart2(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenCart2DoneVal;
  else {
    PGen_LatticeRadialHeap_StateData *s = (PGen_LatticeRadialHeap_StateData *) g->stateData;
    QPMS_ENSURE(s->sdim <= 2, "Attemted to get a 2D point from %nD generator.", (int)(s->sdim));
    double target[2] = {0,0};
    if (PGen_LatticeRadialHeap_fillNext_cart(g, target) == 0 /* there are still points to be generated */) {
      PGenCart2ReturnData retval = {PGEN_COORDS_CART2 | PGEN_NOTDONE, cart2_from_double_array(target)};
      return retval;
    } else { // Maybe more checking for errors?
      PGen_destroy(g);
      return PGenCart2DoneVal;
    }
  }
}

// Extractor, 3D cartesian coordinate output
PGenCart3ReturnData PGen_LatticeRadialHeap3D_next_cart3(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenCart3DoneVal;
  else {
    PGen_LatticeRadialHeap_StateData *s = (PGen_LatticeRadialHeap_StateData *) g->stateData;
    QPMS_ENSURE(s->sdim <= 3, "Attemted to get a 3D point from %nD generator.", (int)(s->sdim));
    double target[3] = {0,0,0};
    if (PGen_LatticeRadialHeap_fillNext_cart(g, target) == 0 /* there are still points to be generated */) {
      PGenCart3ReturnData retval = {PGEN_COORDS_CART3 | PGEN_NOTDONE, cart3_from_double_array(target)};
      return retval;
    } else { // Maybe more checking for errors?
      PGen_destroy(g);
      return PGenCart3DoneVal;
    }
  }
}

// Class metadata structure; TODO maybe this can rather be done by macro.
const PGenClassInfo PGen_LatticeRadialHeap2D = {
  "PGen_LatticeRadialHeap2D", // 2D labels the space, not lattice dimension
  2, //dimensionality
  PGEN_COORDS_CART2, // native coordinate system
  // some of the _next_... fun pointers can be NULL
  NULL, //PGen_LatticeRadialHeap2D_next,
  NULL, //PGen_LatticeRadialHeap2D_next_z,
  PGen_next_pol_from_cart2, // NULL, //PGen_LatticeRadialHeap2D_next_pol,
  PGen_next_sph_from_cart2, // NULL, //PGen_LatticeRadialHeap2D_next_sph,
  PGen_LatticeRadialHeap2D_next_cart2,
  PGen_next_cart3_from_cart2xy, // NULL, //PGen_LatticeRadialHeap2D_next_cart3,
  NULL, //PGen_LatticeRadialHeap2D_fetch,
  NULL, //PGen_LatticeRadialHeap2D_fetch_z,
  NULL, //PGen_LatticeRadialHeap2D_fetch_pol,
  NULL, //PGen_LatticeRadialHeap2D_fetch_sph,
  NULL, //PGen_LatticeRadialHeap2D_fetch_cart2,
  NULL, //PGen_LatticeRadialHeap2D_fetch_cart3,
  PGen_LatticeRadialHeap_destructor
};

// Class metadata structure; TODO maybe this can rather be done by macro.
const PGenClassInfo PGen_LatticeRadialHeap3D = {
  "PGen_LatticeRadialHeap3D", // 3D labels the space, not lattice dimension
  3, //dimensionality
  PGEN_COORDS_CART3, // native coordinate system
  // some of the _next_... fun pointers can be NULL
  NULL, //PGen_LatticeRadialHeap3D_next,
  NULL, //PGen_LatticeRadialHeap3D_next_z,
  NULL, //PGen_LatticeRadialHeap3D_next_pol,
  PGen_next_sph_from_cart3, // NULL, //PGen_LatticeRadialHeap3D_next_sph,
  NULL, //PGen_LatticeRadialHeap3D_next_cart2,
  PGen_LatticeRadialHeap3D_next_cart3,
  NULL, //PGen_LatticeRadialHeap3D_fetch,
  NULL, //PGen_LatticeRadialHeap3D_fetch_z,
  NULL, //PGen_LatticeRadialHeap3D_fetch_pol,
  NULL, //PGen_LatticeRadialHeap3D_fetch_sph,
  NULL, //PGen_LatticeRadialHeap3D_fetch_cart2,
  NULL, //PGen_LatticeRadialHeap3D_fetch_cart3,
  PGen_LatticeRadialHeap_destructor
};


//==== PGen_shifted ====
// Meta-generator that takes another generator and generates points shifted by a constant

extern const PGenClassInfo PGen_shifted; // forward declaration needed by constructor (may be placed in header file instead)

// Internal state structure
typedef struct PGen_shifted_StateData {
  PGen orig;
  cart3_t shift;
} PGen_shifted_StateData;

// Constructor
PGen PGen_shifted_new(PGen orig, cart3_t shift) {
  PGen_shifted_StateData *s = malloc(sizeof(PGen_shifted_StateData));
  s->shift = shift;
  s->orig = orig;
  PGen g = {&PGen_shifted, (void *) s};
  return g;
}

// Dectructor
void PGen_shifted_destructor(PGen *g) {
  PGen_shifted_StateData *s = g->stateData;
  if(s->orig.stateData) PGen_destroy(&(s->orig));
  free(g->stateData);
  g->stateData = NULL;
}

// Extractor, 3D cartesian coordinate output
PGenCart3ReturnData PGen_shifted_next_cart3(PGen *g) {
  if (g->stateData == NULL) // already destroyed
    return PGenCart3DoneVal;
  else {
    PGen_shifted_StateData *s = (PGen_shifted_StateData *) g->stateData;
    PGenCart3ReturnData retdata = PGen_next_cart3(&(s->orig));
    if ((retdata.flags & PGEN_NOTDONE)) {
      retdata.point_cart3 = cart3_add(retdata.point_cart3, s->shift);
      retdata.flags = PGEN_COORDS_CART3 | PGEN_NOTDONE; //TODO more advanced flags?
      return retdata;
    } else {
      PGen_destroy(g);
      return retdata;
    }
  }
}

// Class metadata structure; TODO maybe this can rather be done by macro.
const PGenClassInfo PGen_shifted = {
  "PGen_shifted",
  3, //dimensionality
  PGEN_COORDS_CART3, // native coordinate system
  // some of the _next_... fun pointers can be NULL
  NULL, //PGen_shifted_next,
  NULL, //PGen_shifted_next_z,
  NULL, //PGen_shifted_next_pol,
  PGen_next_sph_from_cart3, //PGen_shifted_next_sph,
  NULL, //PGen_shifted_next_cart2,
  PGen_shifted_next_cart3, // native
  NULL, //PGen_shifted_fetch,
  NULL, //PGen_shifted_fetch_z,
  NULL, //PGen_shifted_fetch_pol,
  NULL, //PGen_shifted_fetch_sph,
  NULL, //PGen_shifted_fetch_cart2,
  NULL, //PGen_shifted_fetch_cart3, // TODO at least this
  PGen_shifted_destructor
};

