/* vi:set sw=4:

    rbbufcommon.c

    Copyright (c) 2000 Minero Aoki <aamine@dp.u-netsurf.ne.jp>

    This program is free software.
    You can distribute/modify this program under the terms of
    the GNU Lesser General Public License version 2 or later.

*/


/*
==================================================================
                          buffer
==================================================================
*/

#define freeze_check(buf) \
    if (OBJ_FROZEN(buf))                                      \
        rb_raise(rb_eArgError, "can't modify frozen buffer"); \
    else


static void
buffer_mark(p)
    void *p;
{
    buffer_t *buf = (buffer_t*)p;
    rb_gc_mark(buf->vhead);
    rb_gc_mark(buf->vtail);
}

static void
buffer_free(p)
    void *p;
{
    buffer_t *buf = (buffer_t*)p;
    buf->vhead = Qnil;
    buf->vtail = Qnil;
    buf_free(buf);
}

static void
bufmark_mark(p)
    void *p;
{
    rb_gc_mark(((mark_t*)p)->vbuf);
}

static void
bufmark_free(p)
    void *p;
{
    mark_free((mark_t*)p);
}

static VALUE
buffer_s_new(self)
    VALUE self;
{
    buffer_t *buf;
    volatile VALUE v[2];

    buf = buf_new();
    buf->vhead = v[0] = WRAP_MARK(buf->head, MARK_CLASS);
    OBJ_FREEZE(buf->vhead);
    buf->vtail = v[1] = WRAP_MARK(buf->tail, MARK_CLASS);
    OBJ_FREEZE(buf->vtail);

    return WRAP_BUF(buf, self);
}

static VALUE
buffer_new_mark(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    buffer_t *buf;
    mark_t *mark;
    VALUE vc, vflag;
    long c;
    unsigned long f;

    rb_scan_args(argc, argv, "02", &vc, &vflag);
    c = NIL_P(vc) ? 0 : NUM2LONG(vc);
    f = sym2iflag(vflag);

    GET_BUF(self, buf);
    CRAMP_CIDX(buf, c);
    mark = mark_char_new(buf, c, f);
    mark->vbuf = self;

    return WRAP_MARK(mark, MARK_CLASS);
}


static VALUE
buffer_head(self)
    VALUE self;
{
    buffer_t *buf;

    GET_BUF(self, buf);
    return buf->vhead;
}

static VALUE
buffer_tail(self)
    VALUE self;
{
    buffer_t *buf;

    GET_BUF(self, buf);
    return buf->vtail;
}


static VALUE
buffer_len(self)
    VALUE self;
{
    buffer_t *buf;

    GET_BUF(self, buf);
    return INT2NUM(LEN(buf));
}

static VALUE
buffer_clen(self)
    VALUE self;
{
    buffer_t *buf;

    GET_BUF(self, buf);
    return INT2NUM(CLEN(buf));
}

static VALUE
buffer_empty_p(self)
    VALUE self;
{
    buffer_t *buf;

    GET_BUF(self, buf);
    return LEN(buf) ? Qfalse : Qtrue;
}


static VALUE
buffer_concat(self, str)
    VALUE self, str;
{
    buffer_t *buf;

    freeze_check(self);
    GET_BUF(self, buf);
    mark_insert(buf->tail, RSTRING(str)->ptr, RSTRING(str)->len);

    return self;
}

static VALUE
buffer_clear(self)
    VALUE self;
{
    buffer_t *buf;

    freeze_check(self);
    GET_BUF(self, buf);
    mark_delete(buf->head, LEN(buf));
    return self;
}


#ifdef IS_TEXT_BUFFER
static VALUE
buffer_substr(self, cidx, clen)
    VALUE self, cidx, clen;
{
    struct sf_textbuf *buf;
    long ci, len;

    GET_BUF(self, buf);
    ci = NUM2LONG(cidx);
    CRAMP_CIDX(buf, ci);
    if (buf->point->cidx > ci)
        mark_back_char(buf->point, buf->point->cidx - ci);
    else
        mark_forward_char(buf->point, ci - buf->point->cidx);
    len = byte_length(buf, buf->point->idx, NUM2LONG(clen), 0);

    return substr(buf, buf->point->idx, len);
}

#else

static VALUE
buffer_substr(self, idx, len)
    VALUE self, idx, len;
{
    struct sf_strbuf *buf;
    long i, le;

    GET_BUF(self, buf);
    i = NUM2LONG(idx);
    CRAMP_IDX(buf, i);
    le = NUM2LONG(len);
    if (le >= 0) {
        CRAMP_LEN(buf, i, le);
    }
    else {
        le = -le;
        if (le > i)
            le = i;
        i -= le;
    }

    return substr(buf, i, le);
}
#endif

static VALUE
buffer_each_line(self)
    VALUE self;
{
    buffer_t *buf;

    GET_BUF(self, buf);
    each_line(buf, 0);
    return Qnil;
}

static VALUE
buffer_rev_each_line(self)
    VALUE self;
{
    buffer_t *buf;

    GET_BUF(self, buf);
    r_each_line(buf, 0);
    return Qnil;
}

static VALUE
buffer_string(self)
    VALUE self;
{
    buffer_t *buf;

    GET_BUF(self, buf);
    return substr(buf, 0, LEN(buf));
}


static void
get_prr(len, pos, range, rev)
    long len;
    long *pos, *range;
    int *rev;
{
    if (*pos > len) {
        *pos = -1;
        return;
    }
    if (*pos < 0) {
        *pos += len;
        if (*pos < 0)
            return;
    }

    if (*range < 0) {
        *range = -(*range);
        *rev = -(*rev);
    }
    if (*rev) {
        if (*range > *pos)
            *range = *pos;
    }
    else {
        if (*pos + *range > len)
            *range = len - *pos;
    }
}

static VALUE
buffer_index(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    buffer_t *buf;
    VALUE vre, vpos, vrange;
    long pos, range, ret;
    int rev;

    GET_BUF(self, buf);
    rb_scan_args(argc, argv, "12", &vre, &vpos, &vrange);

    pos = NIL_P(vpos) ? 0 : NUM2LONG(vpos);
    range = NIL_P(vrange) ? CLEN(buf) : NUM2LONG(vrange);
    rev = 0;
    get_prr(CLEN(buf), &pos, &range, &rev);
    if (pos < 0)
        return Qnil;

    ret = buf_search(buf, vre, pos, range, rev, 0);
    return ret < 0 ? Qnil : INT2NUM(ret);
}

static VALUE
buffer_rindex(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    buffer_t *buf;
    VALUE vre, vpos, vrange;
    long pos, range, ret;
    int rev;

    GET_BUF(self, buf);
    rb_scan_args(argc, argv, "12", &vre, &vpos, &vrange);

    pos = NIL_P(vpos) ? 0 : NUM2LONG(vpos);
    range = NIL_P(vrange) ? CLEN(buf) : NUM2LONG(vrange);
    rev = 1;
    get_prr(CLEN(buf), &pos, &range, &rev);
    if (pos < 0)
        return Qnil;

    ret = buf_search(buf, vre, pos, range, rev, 0);
    return ret < 0 ? Qnil : INT2NUM(ret);
}

static VALUE
buffer_match_to(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    buffer_t *buf;
    VALUE vre, vpos, vrange;
    long pos, range;
    int rev;
    struct re_registers *regi;

    GET_BUF(self, buf);
    rb_scan_args(argc, argv, "12", &vre, &vpos, &vrange);

    pos = NIL_P(vpos) ? 0 : NUM2LONG(vpos);
    range = NIL_P(vrange) ? CLEN(buf) : NUM2LONG(vrange);
    rev = 0;
    get_prr(CLEN(buf), &pos, &range, &rev);
    if (pos < 0)
        return Qnil;

    buf_search(buf, vre, pos, range, rev, &regi);
    return bufregi_new(regi);
}


#ifdef IS_TEXT_BUFFER
static VALUE
buffer_inspect(self)
    VALUE self;
{
    struct sf_textbuf *buf;
    char tmp[128];
    int len;

    GET_BUF(self, buf);
    len = sprintf(tmp,
      "#<TextBuffer: 0x%lx len=%ld gap=%ld glen=%ld L%ld C%ld>",
      (unsigned long)buf,
      buf->len, buf->gap, buf->gaplen, LEN(buf), CLEN(buf));
    return rb_str_new(tmp, len);
}

#else

static VALUE
buffer_inspect(self)
    VALUE self;
{
    struct sf_strbuf *buf;
    char tmp[128];
    int len;

    GET_BUF(self, buf);
    len = sprintf(tmp,
      "#<StringBuffer: 0x%lx len=%ld gap=%ld glen=%ld L%ld>",
      (unsigned long)buf,
      buf->len, buf->gap, buf->gaplen, LEN(buf));
    return rb_str_new(tmp, len);
}
#endif


/*
========================================================================
                           mark
========================================================================
*/

#define bufmark_p(v) \
    ((TYPE(v) == T_DATA) && (RDATA(v)->dfree == bufmark_free))

#define origin_check(m1, m2) \
    if (m1->buf != m2->buf) \
        rb_raise(rb_eArgError, "marks belonging to different buffer used"); \
    else

#undef freeze_check
#define freeze_check(m) \
    if (OBJ_FROZEN(m)) \
        rb_raise(rb_eArgError, "can't move frozen mark"); \
    else

#define alive_check(mark) \
    if (!ALIVE_P(mark)) \
        rb_raise(rb_eArgError, "method called for dead mark"); \
    else


static void
begm_len(mark, val, mvar, lvar)
    mark_t *mark;
    VALUE val;
    mark_t **mvar;
    long *lvar;
{
    if (bufmark_p(val)) {
        mark_t *m;

        GET_MARK_CHK(val, m);
        origin_check(mark, m);
        if (m->idx < mark->idx) {
            *mvar = m;
            *lvar = mark->idx - m->idx;
        }
        else {
            *mvar = mark;
            *lvar = m->idx - mark->idx;
        }
    }
    else {
        long tmp = NUM2LONG(val);

        if (tmp >= 0) {
            CRAMP_CLEN(mark->buf, CIDX(mark), tmp);
            *mvar = mark;
        }
        else {
            CRAMP_CLEN_NEG(mark->buf, CIDX(mark), tmp);
            mark_goto(mark->buf->point, CIDX(mark) - tmp);
            *mvar = mark->buf->point;
        }
        *lvar = byte_length(mark->buf, (*mvar)->idx, tmp, 0);
    }
}


static VALUE
bufmark_buffer(self)
    VALUE self;
{
    mark_t *mark;
    
    GET_MARK_CHK(self, mark);
    return mark->vbuf;
}


static VALUE
bufmark_dup(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    mark_t *mark, *p;
    VALUE clone;
    VALUE vflag;
    unsigned long flag = INSERT_BEFORE;

    if (rb_scan_args(argc, argv, "01", &vflag) == 1) {
        flag = sym2iflag(vflag);
    }
    GET_MARK_CHK(self, mark);
    p = mark_new_com(mark->buf, IDX(mark), CIDX(mark), mark->line,
                     flag);
    clone = WRAP_MARK(p, CLASS_OF(self));
    CLONESETUP(clone, self);

    return clone;
}

static VALUE
bufmark_clone(self)
    VALUE self;
{
    VALUE tmp = bufmark_dup(self);
    if (OBJ_FROZEN(self)) OBJ_FREEZE(tmp);
    return tmp;
}


static VALUE
bufmark_alive_p(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK(self, mark);
    return ALIVE_P(mark) ? Qtrue : Qfalse;
}

static VALUE
bufmark_discard(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK(self, mark);
    remove_mark(mark);
    mark->flags &= ~ALIVE_FLAG;
    mark->buf = 0;
    mark->vbuf = Qnil;

    return self;
}


static VALUE
bufmark_insert(self, str)
    VALUE self, str;
{
    mark_t *mark;
    
    GET_MARK_CHK(self, mark);
    mark_insert(mark, RSTRING(str)->ptr, RSTRING(str)->len);
    return self;
}

#if 0
static void sbm_beg_len _((mark_t*, VALUE, long*, long*));
static void tbm_begm_len _((mark_t*, VALUE, mark_t**, long*));

static VALUE
bufmark_copyof(self, i, l)
    VALUE self, i, l;
{
    mark_t *mark, *m;
    buffer_t *buf;
    long beg, len;
    void (*func)(mark_t*, char*, long);

    GET_MARK_CHK(self, mark);
    if (! bufmark_p(i))
        rb_raise(rb_eTypeError, "wrong arg type %s (expected BufferMark)",
                 rb_class2name(CLASS_OF(i)));
    GET_MARK_CHK(i, m);
    buf = m->buf;

    if (TEXTBUFMARK_P(m)) {
        mark_t *begm;

        tbm_begm_len(m, l, &begm, &len);
        beg = begm->idx;
        func = mark_insert;
    }
    else {
        sbm_beg_len(m, l, &beg, &len);
        func = smark_insert;
    }
    if (beg <= buf->gap && beg + len > buf->gap) {
        (*func)(mark,
                buf->ptr + REAL_INDEX(buf, beg),
                buf->gap - beg);
        (*func)(mark,
                buf->ptr + REAL_INDEX(buf, buf->gap),
                beg + len - buf->gap);
    }
    else {
        (*func)(mark, buf->ptr + REAL_INDEX(buf, beg), len);
    }

    return self;
}
#endif

static VALUE
bufmark_delete(self, val)
    VALUE self, val;
{
    mark_t *mark, *m;
    long len;

    GET_MARK_CHK(self, mark);
    begm_len(mark, val, &m, &len);
    mark_delete(m, len);

    return self;
}

static VALUE
bufmark_substr(self, val)
    VALUE self, val;
{
    mark_t *mark, *m;
    long len;

    GET_MARK_CHK(self, mark);
    begm_len(mark, val, &m, &len);

    return substr(mark->buf, m->idx, len);
}

static VALUE
bufmark_rosubstr(self, val)
    VALUE self, val;
{
    mark_t *mark, *m;
    long len;

    GET_MARK_CHK(self, mark);
    begm_len(mark, val, &m, &len);

    substr_ro(mark->buf, m->idx, len);
    return Qnil;
}

static VALUE
bufmark_roeach(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    each_line_ro(mark->buf, mark->idx);

    return self;
}


static VALUE
bufmark_fwd(self, len)
    VALUE self, len;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    mark_move(mark, NUM2LONG(len));

    return self;
}

static VALUE
bufmark_back(self, v)
    VALUE self, v;
{
    mark_t *mark;
    long len;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    len = NUM2LONG(v);
    mark_move(mark, -len);

    return self;
}


static VALUE
bufmark_char_index(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    return INT2NUM(CIDX(mark));
}

static VALUE
bufmark_byte_index(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    return INT2NUM(IDX(mark));
}

static VALUE
bufmark_setidx(self, idx)
    VALUE self;
{
    mark_t *mark;
    long i = 0;

    GET_MARK_CHK(self, mark);
    freeze_check(self);

    if (FIXNUM_P(idx)) {
        i = FIX2LONG(idx);
    }
    else if (bufmark_p(idx)) {
        mark_t *m;

        GET_MARK_CHK(idx, m);
        origin_check(mark, m);
        mark_moveto(mark, m);

        return self;
    }
    else {
        i = NUM2LONG(idx);
    }

    mark_goto(mark, i);

    return self;
}


static VALUE
bufmark_skip(argc, argv, self)
    int argc;
    VALUE *argv;
    VALUE self;
{
    mark_t *mark;
    VALUE vre, vrange, vrev;
    long range;
    int rev, ret;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    
    rb_scan_args(argc, argv, "12", &vre, &vrange, &vrev);
    rev = RTEST(vrev);
    if (NIL_P(vrange)) {
        if (rev)
            range = IDX(mark);
        else
            range = LEN(mark->buf) - IDX(mark);
    }
    else {
        range = NUM2LONG(vrange);
    }

    if (!mark->regi) {
        mark->regi = ALLOC(struct re_registers);
        memset(mark->regi, 0, sizeof(struct re_registers));
    }
    ret = buf_search(mark->buf, vre, mark->idx, range, rev, &mark->regi);
    if (ret >= 0) {
        mark_goto(mark, (rev ? mark->regi->beg : mark->regi->end)[0]);
        return INT2FIX(ret);
    }
    else {
        return Qnil;
    }
}


typedef struct {
    long capa;
    mark_t **beg;
    mark_t **end;
} bufregi_t;

VALUE
defun(mark_full_match)(self, re, range, rev, regi)
    VALUE self, range, rev;
    bufregi_t *regi;
{
    VALUE argv[3], ret;

    argv[0] = re;
    argv[1] = range;
    argv[2] = rev;
    ret = bufmark_skip(3, argv, self);
    if (NIL_P(ret)) {
        return Qnil;
    }
    else {
        mark_t *mark;
        long i;

        GET_MARK(self, mark);
        if (regi->capa < mark->regi->num_regs) {
            regi->capa = mark->regi->num_regs;
            REALLOC_N(regi->beg, mark_t*, regi->capa * 2);
            regi->end = regi->beg + regi->capa;
        }
        for (i = 0; i < mark->regi->num_regs; i++) {
            if (!regi->beg[i]) {
                regi->beg[i] = mark_char_new(mark->buf, 0, INSERT_BEFORE);
                regi->end[i] = mark_char_new(mark->buf, 0, INSERT_BEFORE);
            }
            mark_goto(regi->beg[i], mark->regi->beg[i]);
            mark_goto(regi->end[i], mark->regi->end[i]);
        }

        return ret;
    }
}


static VALUE
bufmark_go_beg(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    mark_moveto(mark, mark->buf->head);

    return self;
}

static VALUE
bufmark_go_end(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    mark_moveto(mark, mark->buf->tail);

    return self;
}


static VALUE
bufmark_plus(self, idx)
    VALUE self, idx;
{
    mark_t *mark, *m;
    VALUE ret;

    GET_MARK_CHK(self, mark);
    ret = bufmark_dup(self);
    GET_MARK(ret, m);
    mark_move(mark, NUM2LONG(idx));

    return ret;
}

static VALUE
bufmark_minus(self, idx)
    VALUE self, idx;
{
    mark_t *mark, *m;
    VALUE ret;
    long i;

    GET_MARK_CHK(self, mark);
    ret = bufmark_dup(self);
    GET_MARK(ret, m);
    i = NUM2LONG(idx);
    mark_move(mark, -i);

    return ret;
}


#ifdef IS_TEXT_BUFFER
static VALUE
bufmark_line(self)
    VALUE self;
{
    struct sf_textbufmark *mark;

    GET_MARK_CHK(self, mark);
    return INT2NUM(mark->line);
}

static VALUE
bufmark_setline(self, n)
    VALUE self;
{
    
    struct sf_textbufmark *mark;
    long i;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    i = NUM2LONG(n);
    if (mark->line > i) {
        mark_back_nline(mark, mark->line - i);
    }
    else {
        mark_forward_nline(mark, i - mark->line);
    }

    return n;
}
#endif


static VALUE
bufmark_next_line(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    mark_nextline(mark);

    return self;
}

static VALUE
bufmark_prev_line(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    mark_prevline(mark);

    return self;
}


static VALUE
bufmark_line_head(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    mark_linehead(mark);

    return self;
}

static VALUE
bufmark_line_tail(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    mark_linetail(mark);

    return self;
}


static VALUE
bufmark_getline(self)
    VALUE self;
{
    mark_t *mark;

    GET_MARK_CHK(self, mark);

    return getline(mark->buf, mark->idx);
}


static VALUE
bufmark_col(self)
    VALUE self;
{
    mark_t *mark;
    long i;

    GET_MARK_CHK(self, mark);
    i = mark_col(mark);
    return INT2NUM(i);
}

static VALUE
bufmark_bytecol(self)
    VALUE self;
{
    mark_t *mark;
    long i;

    GET_MARK_CHK(self, mark);
    i = mark_bcol(mark);
    return INT2NUM(i);
}

static VALUE
bufmark_setcol(self, n)
    VALUE self;
{
    
    mark_t *mark;
    long i;

    GET_MARK_CHK(self, mark);
    freeze_check(self);
    i = NUM2LONG(n);
    mark_setcol(mark, i);

    return n;
}


#ifdef IS_TEXT_BUFFER
static VALUE
bufmark_inspect(self)
    VALUE self;
{
    mark_t *mark;
    char buf[128];
    int len;

    GET_MARK(self, mark);
    if (ALIVE_P(mark)) {
        len = sprintf(buf, "#<TextBufferMark: 0x%lx buf=0x%lx i=%ld ci=%ld>",
                      (unsigned long)mark, (unsigned long)mark->buf,
                      mark->idx, mark->cidx);
    }
    else {
        len = sprintf(buf, "#<TextBufferMark: 0x%lx dead>",
                      (unsigned long)mark);
    }
    return rb_str_new(buf, len);
}

#else

static VALUE
bufmark_inspect(self)
    VALUE self;
{
    mark_t *mark;
    char buf[128];
    int len;

    GET_MARK(self, mark);
    if (ALIVE_P(mark)) {
        len = sprintf(buf, "#<StringBufferMark: 0x%lx buf=0x%lx i=%ld>",
                      (unsigned long)mark, (unsigned long)mark->buf,
                      mark->idx);
    }
    else {
        len = sprintf(buf, "#<StringBufferMark: 0x%lx dead>",
                      (unsigned long)mark);
    }
    return rb_str_new(buf, len);
}
#endif
