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"
#define NEED_newRV_noinc
#define NEED_sv_2pv_flags
#include "ppport.h"

#include <libstree.h>

int
redirect_stderr () {
    return dup2(fileno(stdout), fileno(stderr));
}

void
restore_stderr (int old) {
    if (old != -1) dup2(old, fileno(stderr));
}

LST_Node *
follow_string (LST_STree *tree, LST_String *string) {
    LST_Node *node = tree->root_node;
    LST_Edge *edge = NULL;
    u_int done = 0, len, common;
    u_int todo = string->num_items;

    while (todo > 0) {
        for (edge = node->kids.lh_first; edge; edge = edge->siblings.le_next) {
            if (lst_string_eq(edge->range.string, edge->range.start_index,
                              string, done))
            {
                break;
            }
        }
        if (! edge) {
            break;
        }

        len = lst_edge_get_length(edge);
        common = lst_string_items_common(edge->range.string,
                                         edge->range.start_index, string, done,
                                         len);
        done += common;
        todo -= common;
        node = edge->dst_node;
    }

    return (done < string->num_items - 1) ? NULL : node;
}

typedef LST_STree *Tree__Suffix;

MODULE = Tree::Suffix  PACKAGE = Tree::Suffix

Tree::Suffix
new (class, ...)
    char *class
PROTOTYPE: $;@
PREINIT:
    LST_STree *self;
    IV i;
    STRLEN len;
    char *string;
CODE:
    self = lst_stree_new(NULL);
    if (! self) {
        XSRETURN_UNDEF;
    }
    for (i = 1; i < items; i++) {
        if (! SvOK(ST(i))) {
            continue;
        }
        string = SvPV(ST(i), len);
        lst_stree_add_string(self, lst_string_new(string, 1, len));
    }
    RETVAL = self;
OUTPUT:
    RETVAL

IV
allow_duplicates (self, flag=&PL_sv_yes)
    Tree::Suffix self
    SV *flag
PROTOTYPE: $;$
CODE:
    if (items == 2) {
        lst_stree_allow_duplicates(self, SvTRUE(flag));
    }
    RETVAL = self->allow_duplicates;
OUTPUT:
    RETVAL

IV
insert (self, ...)
    Tree::Suffix self
PROTOTYPE: $@
PREINIT:
    STRLEN len;
    char *string;
    IV i, pre;
CODE:
    if (items == 1) {
        XSRETURN_IV(0);
    }
    pre = self->num_strings;
    for (i = 1; i < items; i++) {
        if (! SvOK(ST(i))) {
            continue;
        }
        string = SvPV(ST(i), len);
        lst_stree_add_string(self, lst_string_new(string, 1, len));
    }
    XSRETURN_IV(self->num_strings - pre);

void
strings (self)
    Tree::Suffix self
PROTOTYPE: $
PREINIT:
    LST_StringHash *hash;
    LST_StringHashItem *hi;
    IV i;
PPCODE:
    if (GIMME_V != G_ARRAY) {
        XSRETURN_IV(self->num_strings);
    }
    EXTEND(SP, self->num_strings);
    for (i = 0; i < LST_STRING_HASH_SIZE; i++) {
        hash = &self->string_hash[i];
        for (hi = hash->lh_first; hi; hi = hi->items.le_next) {
            PUSHs(sv_2mortal(newSViv(hi->index)));
        }
    }

IV
nodes (self)
    Tree::Suffix self
PROTOTYPE: $
CODE:
    XSRETURN_IV(self->root_node->num_kids);

void
clear (self)
    Tree::Suffix self
PROTOTYPE: $
CODE:
    lst_stree_clear(self);
    lst_stree_init(self);

void
dump (self)
    Tree::Suffix self
PROTOTYPE: $
PREINIT:
    IV fn;
CODE:
    /* Redirect from stderr to stdout */
    fn = redirect_stderr();
    lst_debug_print_tree(self);
    restore_stderr(fn);

IV
remove (self, ...)
    Tree::Suffix self
PROTOTYPE: $@
PREINIT:
    LST_StringHash *hash;
    LST_StringHashItem *hi;
    LST_String *str;
    STRLEN len;
    char *string;
    IV i, j, k, done = 0;
CODE:
    for (i = 1; i < items; i++) {
        if (! SvOK(ST(i))) {
            continue;
        }
        string = SvPV(ST(i), len);
        str = lst_string_new(string, 1, len);
        /* Check each hash bucket for the string.  Would it be better to use
        *  find() ?
        */
        for (j = 0; j < LST_STRING_HASH_SIZE; j++) {
            hash = &self->string_hash[j];
            for (hi = hash->lh_first; hi; hi = hi->items.le_next) {
                if (lst_string_get_length(hi->string) != len) {
                    continue;
                }
                for (k = 0; k < len && lst_string_eq(str, k, hi->string, k);
                     k++);
                if (k == len) {
                    lst_stree_remove_string(self, hi->string);
                    done++;
                    if (! self->allow_duplicates) {
                        goto next_item;
                    }
                }
            }
        }
        next_item: 1;
        lst_string_free(str);
    }
    XSRETURN_IV(done);

void
_algorithm_longest_substrings (self, min_len=0, max_len=0)
    Tree::Suffix self
    IV min_len
    IV max_len
ALIAS:
    lcs = 1
    longest_common_substrings = 2
    lrs = 3
    longest_repeated_substrings = 4
PROTOTYPE: $;$$
PREINIT:
    LST_StringSet *res;
    LST_String *str;
PPCODE:
    if (ix > 2) {
        res = lst_alg_longest_repeated_substring(self, min_len, max_len);
    }
    else {
        res = lst_alg_longest_common_substring(self, min_len, max_len);
    }
    if (res) {
        EXTEND(SP, res->size);
        for (str = res->members.lh_first; str; str = str->set.le_next) {
            PUSHs(sv_2mortal(newSVpv((char *)lst_string_print(str), 0)));
        }
        lst_stringset_free(res);
    }

void
find (self, string)
    Tree::Suffix self
    SV *string
ALIAS:
    match = 1
    search = 2
PROTOTYPE: $$
PREINIT:
    LST_String *str;
    LST_Edge *edge;
    LST_Node *node;
    TAILQ_HEAD(shead, lst_node) stack;
    AV *match;
    STRLEN len = 0;
PPCODE:
    if (SvOK(string)) {
        len = SvCUR(string);
    }
    if (len < 1) {
        GIMME_V == G_ARRAY ? XSRETURN_EMPTY : XSRETURN_IV(0);
    }
    str = lst_string_new(SvPV_nolen(string), 1, len);
    node = follow_string(self, str);
    lst_string_free(str);
    if (! node) {
        GIMME_V == G_ARRAY ? XSRETURN_EMPTY : XSRETURN_IV(0);
    }
    /* Perform a depth-first search from matching node to find leafs. */
    TAILQ_INIT(&stack);
    TAILQ_INSERT_HEAD(&stack, node, iteration);
    while (node = stack.tqh_first) {
        TAILQ_REMOVE(&stack, stack.tqh_first, iteration);
        if (lst_node_is_leaf(node)) {
            match = (AV *)newAV();
            av_extend(match, 3);
            av_push(match, newSViv(lst_stree_get_string_index(self, node->up_edge->range.string)));
            av_push(match, newSViv(node->index));
            av_push(match, newSViv(node->index + len - 1));
            XPUSHs(sv_2mortal(newRV_noinc((SV *)match)));
        }
        for (edge = node->kids.lh_first; edge; edge = edge->siblings.le_next) {
            TAILQ_INSERT_HEAD(&stack, edge->dst_node, iteration);
        }
    }
    if (GIMME_V == G_SCALAR) {
        XSRETURN_IV(SP - MARK);
    }

SV *
string (self, idx, start=0, end=-1)
    Tree::Suffix self
    IV idx
    IV start
    IV end
PROTOTYPE: $$;$$
PREINIT:
    LST_StringHash *hash;
    LST_StringHashItem *hi;
    LST_StringIndex range;
    IV i;
CODE:
    for (i = 0; i < LST_STRING_HASH_SIZE; i++) {
        hash = &self->string_hash[i];
        for (hi = hash->lh_first; hi && hi->index != idx;
            hi = hi->items.le_next);
        if (hi && hi->index == idx) {
            break;
        }
    }
    if (! hi) {
        XSRETURN_NO;
    }

    lst_string_index_init(&range);
    range.string = hi->string;

    if (items < 4) {
        end = hi->string->num_items - 1;
    }
    if (start < 0) {
        start = 0;
    }
    /* Avoid print_func from returning "<eos>" */
    else if (start == hi->string->num_items - 1) {
        start++;
    }
    if (end < start) {
        XSRETURN_NO;
    }
    range.start_index = start;
    *(range.end_index) = end;
    RETVAL = newSVpv(hi->string->sclass->print_func(&range), 0);
OUTPUT:
    RETVAL

void
DESTROY (self)
    Tree::Suffix self
PROTOTYPE: $
CODE:
    lst_stree_free(self);