The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"

typedef struct {
    AV  *grid;
    int rows;
    int columns;
    int dirty;
} grid_t;

typedef grid_t *Grid__Transform;

/* The dimensions or array may be changed by the user at any time, so this
   must alway be called before making use of the grid's array to ensure it is
   rectangular.  This is done by resizing the array- in essence, adding or
   removing extra elements.  The resizing is done in a non-destructive way so
   the removed elements are again available if the dimensions are later
   altered. */
#define FIX_DIRTY_GRID(g)                                                      \
    if ((g)->dirty) {                                                          \
        IV len = av_len((g)->grid);                                            \
        IV add = (g)->rows * (g)->columns - len - 1;                           \
        av_extend((g)->grid, len + add);                                       \
        AvFILLp((g)->grid) += add;                                             \
        if (add > 0) {                                                         \
            IV i;                                                              \
            /* Find position after last extra element. */                      \
            /* TODO: binary search? */                                         \
            for (i=len+1; i<=AvFILLp((g)->grid); i++) {                        \
                if (AvARRAY((g)->grid)[i] == &PL_sv_undef) break;              \
            }                                                                  \
            for (; i<=AvFILLp((g)->grid); i++) {                               \
                AvARRAY((g)->grid)[i] =  newSVpvn("", 0);                      \
            }                                                                  \
        }                                                                      \
       (g)->dirty = 0;                                                         \
    }

#define SWAP(type,a,b)                                                         \
    STMT_START {                                                               \
        type tmp = (a);                                                        \
        (a) = (b);                                                             \
        (b) = tmp;                                                             \
    } STMT_END

#define REVERSE_AV(av)                                                         \
    STMT_START {                                                               \
        SV **s = AvARRAY((av));                                                \
        SV **e = s + av_len((av));                                             \
        while (s < e) {                                                        \
            SV *tmp = *s;                                                      \
            *s++ = *e;                                                         \
            *e-- = tmp;                                                        \
        }                                                                      \
  } STMT_END

MODULE = Grid::Transform  PACKAGE = Grid::Transform

Grid::Transform
new (class, aref, ...)
    char *class
    SV *aref
PROTOTYPE: $\@$$;$$
PREINIT:
    AV *av;
    grid_t *self;
    IV i, len, add = 0;
CODE:
    av = (AV *)SvRV(aref);
    if (! (SvRV(aref) && SvTYPE(SvRV(aref)) == SVt_PVAV)) {
        croak ("reference to an array expected");
    }

    New(0, self, 1, grid_t);

    self->rows = 0;
    self->columns = 0;
    self->dirty = 0;

    for(i=2; i<items; i+=2) { 
        char *key = SvPV(ST(i), PL_na);
        if (strEQ(key, "rows")) {
            self->rows = SvIV(ST(i+1));
        }
        else if (strEQ(key, "columns")) {
            self->columns = SvIV(ST(i+1));
        }
        else {
            croak("unknown key in parameter list: %s", key);
        }
    }

    len = av_len(av);

    if (! self->rows && ! self->columns) {
        croak("no \"rows\" or \"columns\"");
    }
    /* Determine missing dimension. */
    else if (! self->rows) {
        self->rows = (len+1) / self->columns + ((len+1) % self->columns ? 1 : 0);
    }
    else if (! self->columns) {
        self->columns = (len+1) / self->rows + ((len+1) % self->rows ? 1 : 0);
    }
    /* Add or remove extra elements to ensure a rectangular grid. */
    add = self->rows * self->columns - len - 1;

    self->grid = newAV();
    /* add may be negative, so ensure it does't decrease the size of the
       original array. */
    av_extend(self->grid, len + (add > 0 ? add : 0));
    AvFILLp(self->grid) = len + add;

    /* Copy original array. */
    for (i=0; i<=len; i++) {
        AvARRAY(self->grid)[i] = newSVsv(AvARRAY(av)[i]);
    }
    /* Add extra empty elements. */
    for (i=add+len; i>len; i--) {
        AvARRAY(self->grid)[i] = newSVpvn("", 0);
    }
    RETVAL = self;
OUTPUT:
    RETVAL

Grid::Transform
copy (self)
    Grid::Transform self
PROTOTYPE: $
PREINIT:
    grid_t *copy;
CODE:
    New(0, copy, 1, grid_t);
    copy->grid = av_make(av_len(self->grid)+1, AvARRAY(self->grid));
    copy->rows = self->rows;
    copy->columns = self->columns;
    RETVAL = copy;
OUTPUT:
    RETVAL

IV
rows (self, rows=0)
    Grid::Transform self
    IV rows
PROTOTYPE: $;$
CODE:
    if (items == 2) {
        self->rows = rows;
        self->dirty = 1;
    }
    RETVAL = self->rows;
OUTPUT:
    RETVAL

IV
columns (self, columns=0)
    Grid::Transform self
    IV columns
PROTOTYPE: $;$
ALIAS:
    cols = 1
CODE:
    if (items == 2) {
        self->columns = columns;
        self->dirty = 1;
    }
    RETVAL = self->columns;
OUTPUT:
    RETVAL

void
grid (self, aref=0)
    Grid::Transform self
    SV *aref
PROTOTYPE: $;\@
PPCODE:
    if (items == 2) {
        AV *av = (AV *)SvRV(aref);
        if (! (SvRV(aref) && SvTYPE(SvRV(aref)) == SVt_PVAV)) {
            croak ("reference to an array expected");
        }

        SvREFCNT_dec(self->grid);
        self->grid = av_make(av_len(av)+1, AvARRAY(av));
        self->dirty = 1;
    }
    if (GIMME_V != G_VOID) {
        FIX_DIRTY_GRID(self);
        if (GIMME_V == G_ARRAY) {
            IV i, len = av_len(self->grid);
            EXTEND(SP, len + 1);
            for (i=0; i<=len; i++) {
                PUSHs(sv_2mortal(SvREFCNT_inc(AvARRAY(self->grid)[i])));
            }
        }
        else {
            PUSHs(sv_2mortal(newRV_inc((SV *)self->grid)));
        }
    }

void
rotate_180 (self)
    Grid::Transform self
PROTOTYPE: $
ALIAS:
    rotate180 = 1
PPCODE:
    FIX_DIRTY_GRID(self);
    REVERSE_AV(self->grid);
    XSRETURN(1);

void
rotate_90 (self)
    Grid::Transform self
PROTOTYPE: $
ALIAS:
    rotate90 = 1
PREINIT:
    SV **tmp;
    IV n, i, row, col;
PPCODE:
    FIX_DIRTY_GRID(self);
    n = self->rows * self->columns;
    New(0, tmp, n, SV*);
    for (i=0, col=0; col<self->columns; col++) {
        for (row=self->rows-1; row>=0; row--, i++) {
            tmp[i] = AvARRAY(self->grid)[col + row * self->columns];
        }
    }
    for (i=0; i<n; i++) {
        AvARRAY(self->grid)[i] = tmp[i];
    }
    Safefree(tmp);
    SWAP(IV, self->rows, self->columns);
    XSRETURN(1);

void
rotate_270 (self)
    Grid::Transform self
PROTOTYPE: $
ALIAS:
    rotate270 = 1
PREINIT:
    SV **tmp;
    IV n, i, row, col;
PPCODE:
    FIX_DIRTY_GRID(self);
    n = self->rows * self->columns;
    New(0, tmp, n, SV*);
    for (i=0, col=self->columns-1; col>=0; col--) {
        for (row=0; row<self->rows; row++, i++) {
            tmp[i] = AvARRAY(self->grid)[col + row * self->columns];
        }
    }
    for (i=0; i<n; i++) {
        AvARRAY(self->grid)[i] = tmp[i];
    }
    Safefree(tmp);
    SWAP(IV, self->rows, self->columns);
    XSRETURN(1);

void
transpose (self)
    Grid::Transform self
PROTOTYPE: $
PREINIT:
    SV **tmp;
    IV n, i, row, col;
PPCODE:
    FIX_DIRTY_GRID(self);
    n = self->rows * self->columns;
    New(0, tmp, n, SV*);
    for (i=0, col=self->columns-1; col>=0; col--) {
        for (row=self->rows-1; row>=0; row--, i++) {
            tmp[i] = AvARRAY(self->grid)[col + row * self->columns];
        }
    }
    for (i=0; i<n; i++) {
        AvARRAY(self->grid)[i] = tmp[i];
    }
    Safefree(tmp);
    SWAP(IV, self->rows, self->columns);
    XSRETURN(1);

void
counter_transpose (self)
    Grid::Transform self
PROTOTYPE: $
ALIAS:
    countertranspose = 1
PREINIT:
    SV **tmp;
    IV n, i, row, col;
PPCODE:
    FIX_DIRTY_GRID(self);
    n = self->rows * self->columns;
    New(0, tmp, n, SV*);
    for (i=0, col=0; col<self->columns; col++) {
        for (row=0; row<self->rows; row++, i++) {
            tmp[i] = AvARRAY(self->grid)[col + row * self->columns];
        }
    }
    for (i=0; i<n; i++) {
        AvARRAY(self->grid)[i] = tmp[i];
    }
    Safefree(tmp);
    SWAP(IV, self->rows, self->columns);
    XSRETURN(1);

void
flip_horizontal (self)
    Grid::Transform self
PROTOTYPE: $
ALIAS:
    mirror_horizontal = 1
PREINIT:
    IV row, lcol, rcol;
PPCODE:
    FIX_DIRTY_GRID(self);
    for (row=0; row<self->rows; row++) {
        for (lcol=0, rcol=self->columns-1; lcol<rcol; lcol++, rcol--) {
            SWAP(SV*, AvARRAY(self->grid)[lcol + row * self->columns],
                      AvARRAY(self->grid)[rcol + row * self->columns]);
        }
    }
    XSRETURN(1);

void
flip_vertical (self)
    Grid::Transform self
PROTOTYPE: $
ALIAS:
    mirror_vertical = 1
PREINIT:
    IV col, trow, brow;
PPCODE:
    FIX_DIRTY_GRID(self);
    for (col=0; col<self->columns; col++) {
        for (trow=0, brow=self->rows-1; trow<brow; trow++, brow--) {
            SWAP(SV*, AvARRAY(self->grid)[col + trow * self->columns],
                      AvARRAY(self->grid)[col + brow * self->columns]);
        }
    }
    XSRETURN(1);

void fold_right (self)
    Grid::Transform self
PROTOTYPE: $
PREINIT:
    SV **tmp;
    IV n, i, row, col, h;
PPCODE:
    FIX_DIRTY_GRID(self);
    n = self->rows * self->columns;
    New(0, tmp, n, SV*);
    h = self->columns >> 1;
    for (i=0, row=0; row<self->rows; row++) {
        IV cn = row * self->columns;
        if (self->columns & 1) {
            tmp[i++] = AvARRAY(self->grid)[h + cn];
        }
        for (col=h-1; col>=0; col--) {
            tmp[i++] = AvARRAY(self->grid)[col + cn];
            tmp[i++] = AvARRAY(self->grid)[self->columns - 1 - col + cn];
        }
    }
    for (i=0; i<n; i++) {
        AvARRAY(self->grid)[i] = tmp[i];
    }
    Safefree(tmp);
    XSRETURN(1);

void fold_left (self)
    Grid::Transform self
PROTOTYPE: $
PREINIT:
    SV **tmp;
    IV n, i, row, col, h;
PPCODE:
    FIX_DIRTY_GRID(self);
    n = self->rows * self->columns;
    New(0, tmp, n, SV*);
    h = self->columns >> 1;
    for (i=0, row=0; row<self->rows; row++) {
        IV cn = row * self->columns;
        for (col=0; col<h; col++) {
            tmp[i++] = AvARRAY(self->grid)[self->columns - 1 - col + cn];
            tmp[i++] = AvARRAY(self->grid)[col + cn];
        }
        if (self->columns & 1) {
            tmp[i++] = AvARRAY(self->grid)[h + cn];
        }
    }
    for (i=0; i<n; i++) {
        AvARRAY(self->grid)[i] = tmp[i];
    }
    Safefree(tmp);
    XSRETURN(1);

void alternate_row_direction (self)
    Grid::Transform self
PROTOTYPE: $
ALIAS:
    alt_row_dir = 1
PREINIT:
    IV row, lcol, rcol;
PPCODE:
    FIX_DIRTY_GRID(self);
    for (row=1; row<self->rows; row+=2) {
        for (lcol=0, rcol=self->columns-1; lcol<rcol; lcol++, rcol--) {
            SWAP(SV*, AvARRAY(self->grid)[lcol + row * self->columns],
                      AvARRAY(self->grid)[rcol + row * self->columns]);
        }
    }
    XSRETURN(1);

void spiral (self)
    Grid::Transform self
PROTOTYPE: $
PREINIT:
    SV **tmp;
    IV n, i, idx, top, bottom, left, right;
PPCODE:
    FIX_DIRTY_GRID(self);
    n = self->rows * self->columns;
    New(0, tmp, n, SV*);
    idx = 0;
    top = 0;
    bottom = self->rows-1;
    left = 0;
    right = self->columns-1;

    while (1) {
        for (i=left; i<=right; i++) {
            tmp[idx++] = AvARRAY(self->grid)[i + top * self->columns];
        }
        if (++top > bottom) break;

        for (i=top; i<=bottom; i++) {
            tmp[idx++] = AvARRAY(self->grid)[right + i * self->columns];
        }
        if (--right < left) break;

        for (i=right; i>=left; i--) {
            tmp[idx++] = AvARRAY(self->grid)[i + bottom * self->columns];
        }
        if (--bottom < top) break;

        for (i=bottom; i>=top; i--) {
            tmp[idx++] = AvARRAY(self->grid)[left + i * self->columns];
        }
        if (++left > right) break;
    }
    for (i=0; i<n; i++) {
        AvARRAY(self->grid)[i] = tmp[i];
    }
    Safefree(tmp);
    XSRETURN(1);

void
DESTROY (self)
    Grid::Transform self
CODE:
    if (self->grid) {
        SvREFCNT_dec(self->grid);
    }
    Safefree(self);