/* vi:set sw=4:

    sftextbuf.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.

*/


/*

    byte string buffer

                  gap
                   |
                   V
    ptr -> OoOoOoOo--------------OoOoOoOoOoOoOo
                   |<- gaplen ->|
           |<------------ len --------------->|

    O : first byte of multi byte char
    o : rest byte
    - : gap


    gap never divide multi byte char or nl
    nl is /\n|\r\n|\r/
    nl is one char
    number of first line is 1
    byte/char index of buffer-mark DOES NOT include gap length

*/


#ifdef IS_TEXT_BUFFER
typedef  struct sf_textbuf      buffer_t;
typedef  struct sf_textbufmark  mark_t;
typedef  char                   char_t;

#define defun(nm) sf_t ## nm

#define BUFFER_FLAG TEXT_BUFFER

#define LEN(buf) ((buf)->len - (buf)->gaplen)
#define CLEN(buf) ((buf)->tail->cidx)

#define IDX(mark) mark->idx
#define CIDX(mark) mark->cidx

#define C(code) code
#define L(code) code
#endif


#ifdef IS_STRING_BUFFER
typedef struct sf_strbuf      buffer_t;
typedef struct sf_strbufmark  mark_t;
typedef char                  char_t;

#define defun(nm) sf_s ## nm

#define BUFFER_FLAG STRING_BUFFER

#define LEN(buf) ((buf)->len - (buf)->gaplen)
#define CLEN(buf) LEN(buf)

#define IDX(mark) mark->idx
#define CIDX(mark) mark->idx

#define C(code)
#define L(code)
#endif


#ifdef IS_WCHAR_BUFFER
typedef struct sf_wcharbuf      buffer_t;
typedef struct sf_wcharbufmark  mark_t;
typedef unsigned long           char_t;

#define defun(nm) sf_w ## nm

#define BUFFER_FLAG WCHAR_BUFFER

#define LEN(buf) ((buf)->len)
#define CLEN(buf) ((buf)->len)

#define IDX(mark) ((mark)->idx)
#define CIDX(mark) ((mark)->idx)

#define C(code)
#define L(code) code
#endif


static int dodebug = 0;


/* extern functions */

#define buf_new()               defun(buf_new)()
#define buf_free(b)             defun(buf_free)(b)
#define buf_len(b)              defun(buf_len)(b)
#define buf_clen(b)             defun(buf_clen)(b)
#ifdef IS_TEXT_BUFFER
#define mark_new(b,i,c,l,f)     defun(mark_new)(b,i,c,l,f)
#define mark_new_com(b,i,c,l,f) mark_new(b,i,c,l,f)
#else
#define mark_new(b,i,f)         defun(mark_new)(b,i,f)
#define mark_new_com(b,i,c,l,f) mark_new(b,c,f)
#endif
#define mark_char_new(b,c,f)    defun(mark_char_new)(b,c,f)
#define mark_free(m)            defun(mark_free)(m)
#define mark_alive_p(m)         defun(mark_alive_p)(m)
#define mark_move(m,l)          defun(mark_move)(m,l)
#define mark_goto(m,i)          defun(mark_goto)(m,i)
#define mark_moveto(m,to)       defun(mark_moveto)(m,to)
#define mark_insert(m,s,l)      defun(mark_insert)(m,s,l)
#define mark_delete(m,l)        defun(mark_delete)(m,l)
#define mark_substr(m,l)        defun(mark_substr)(m,l)
#define mark_nextline(m)        defun(mark_nextline)(m)
#define mark_prevline(m)        defun(mark_prevline)(m)
#define mark_linetail(m)        defun(mark_linetail)(m)
#define mark_linehead(m)        defun(mark_linehead)(m)
#define mark_forward_nline(m,n) defun(mark_forward_nline)(m,n)
#define mark_back_nline(m,n)    defun(mark_back_nline)(m,n)
#define mark_col(m)             defun(mark_col)(m)
#define mark_bcol(m)            defun(mark_bcol)(m)
#define mark_setcol(m,i)        defun(mark_setcol)(m,i)


/* mark list */
static void    insert_mark_between  _((mark_t *mark,
                                       mark_t *prev, mark_t *next));
static void    remove_mark          _((mark_t *mark));
static void    adjust_mark_forward  _((mark_t *mark));
static void    adjust_mark_backward _((mark_t *mark));
static mark_t* first_mark           _((mark_t *mark));

/* mark */
#ifdef IS_TEXT_BUFFER
static void    mark_forward_char _((mark_t *mark, long clen));
static void    mark_forward_byte _((mark_t *mark, long len));
static void    mark_back_char    _((mark_t *mark, long clen));
static void    mark_back_byte    _((mark_t *mark, long len));
#endif


/* buffer */
static long fwdnl         _((buffer_t *buf, long idx));
static long backnl        _((buffer_t *buf, long idx));
static long linehead      _((buffer_t *buf, long idx));
static long linetail      _((buffer_t *buf, long idx));
static long forward_nline _((buffer_t *buf, long idx, long n));
static long back_nline    _((buffer_t *buf, long idx, long n));
#ifdef IS_TEXT_BUFFER
static long byte_idx      _((buffer_t *buf,
                             long begidx, long clen, long *line));
static long byte_length   _((buffer_t *buf,
                             long begidx, long clen, long *line));
static long char_length   _((buffer_t *buf,
                             long begidx, long len, long *line));
static long char_idx      _((buffer_t *buf,
                             long begidx, long len, long *line));
#endif
static long fwdcol        _((buffer_t *buf, long beg, long clen));

static void setgap        _((buffer_t *buf, long idx));
static void insert        _((buffer_t *buf, long idx, char_t *str, long len));
static void delete        _((buffer_t *buf, long idx, long len));
static VALUE substr       _((buffer_t *buf, long idx, long len));

static VALUE getline      _((buffer_t *buf, long idx));
static void each_line     _((buffer_t *buf, long beg));
static void r_each_line   _((buffer_t *buf, long beg));
static void each_line_ro  _((buffer_t *buf, long idx));
static void substr_ro     _((buffer_t *buf, long idx, long len));

static long buf_index     _((buffer_t *buf, char_t *sub, long slen,
                             long idx, long range));
static long buf_rindex    _((buffer_t *buf, char_t *sub, long slen,
                             long idx, long range));
static long buf_search    _((buffer_t *buf, VALUE vre,
                             long pos, long range, int rev,
                             struct re_registers **regi));

#define INITLEN 4


#define CRITICAL_FLAG (1UL << (TEXTBUF_EXTERN_FLAG_BIT + 1))

#define CRITICAL_CHECK(buf) do { \
    if (buf->flags & CRITICAL_FLAG)                                        \
        rb_raise(rb_eArgError, "can't modify buffer in critical session"); \
} while (0)

#define CRITICAL_BEGIN(buf) do { \
    CRITICAL_CHECK(buf);             \
    buf->flags |= CRITICAL_FLAG;     \
} while (0)

#define CRITICAL_END(buf) buf->flags &= ~CRITICAL_FLAG


#define BUF_LOCK(buf) do { \
    VALUE th = rb_thread_current();     \
    if (buf->lock && buf->lock != th)   \
        rb_thread_schedule();           \
    else                                \
        buf->lock = th;                 \
} while (0)

#define BUF_UNLOCK(buf) buf->lock = Qfalse;


#ifdef DEBUG
#define CRAMP_ASSERT(var, name) do { \
    if (var < 0) {                   \
        fprintf(stderr, "***ERROR*** CRAMP_%s: wrong value %ld\n", \
                name, var);          \
        break;                       \
    }                                \
} while (0)
#else
#define CRAMP_ASSERT(var, name)
#endif

#define CRAMP_IDX(buf, idx) do { \
    CRAMP_ASSERT(idx, "IDX");    \
    if (idx > LEN(buf))          \
        idx = LEN(buf);          \
} while(0)

#define CRAMP_CIDX(buf, idx) do { \
    CRAMP_ASSERT(idx, "CIDX");    \
    if (idx > CLEN(buf))          \
        idx = CLEN(buf);          \
} while(0)

#define CRAMP_LEN(buf, idx, len) do { \
    CRAMP_ASSERT(len, "LEN");         \
    if (idx + len > LEN(buf))         \
        len = LEN(buf) - idx;         \
} while(0)

#define CRAMP_CLEN(buf, idx, len) do { \
    CRAMP_ASSERT(len, "CLEN");         \
    if (idx + len > CLEN(buf))         \
        len = CLEN(buf) - idx;         \
} while(0)

#define CRAMP_LEN_NEG(buf, idx, len) do { \
    len = -len;                           \
    CRAMP_ASSERT(len, "LEN_NEG");         \
    if (len > idx)                        \
        len = idx;                        \
} while(0)

#define CRAMP_CLEN_NEG(buf, idx, len) do { \
    len = -len;                            \
    CRAMP_ASSERT(len, "CLEN_NEG");         \
    if (len > idx)                         \
        len = idx;                         \
} while(0)

#define CANONICAL_BEG_LEN(buf, idx, len) do { \
    if (len >= 0) {                           \
        if (idx + len > LEN(buf))             \
            len = LEN(buf) - idx;             \
    }                                         \
    else {                                    \
        len = -len;                           \
        if (len > idx)                        \
            len = idx;                        \
        idx -= len;                           \
    }                                         \
} while (0)


#ifdef DEBUG
#define MARK_ASSERT(var, condi, name) \
    if (var < 0) {                                                  \
        fprintf(stderr,                                             \
                "***ERROR*** mark_%s: param negative\n",            \
                name);                                              \
        return;                                                     \
    }                                                               \
    if (condi) {                                                    \
        fprintf(stderr,                                             \
                "***ERROR*** mark_%s: param out of range %ld\n",    \
                name, var);                                         \
        return;                                                     \
    }
#else
#define MARK_ASSERT(var, condi, name)
#endif


/*
==================================================================
                             debug
==================================================================
*/

#if defined(DEBUG) || defined(DEBUGB) || defined(DEBUGC) || \
    defined(DEBUGD) || defined(DEBUGI) || defined(DEBUGS)
#ifdef IS_TEXT_BUFFER
static void
p_buf(buf)
    struct sf_textbuf *buf;
{
    printf("#<textbuf 0x%lx len=%ld gap=%ld gaplen=%ld L%ld C%ld>\n",
           (unsigned long)buf, buf->len,
           buf->gap, buf->gaplen, LEN(buf), CLEN(buf));
}

static void
p_mark(mark)
    struct sf_textbufmark *mark;
{
    printf("#<textbufmark 0x%lx buf=0x%lx I%ld C%ld L%ld>\n",
           (unsigned long)mark, (unsigned long)mark->buf,
           mark->idx, mark->cidx, mark->line);
}
#endif

#ifdef IS_STRING_BUFFER
static void
p_buf(buf)
    buffer_t *buf;
{
    printf("#<strbuf 0x%lx len=%ld gap=%ld gaplen=%ld L%ld>\n",
           (unsigned long)buf, buf->len, buf->gap, buf->gaplen, LEN(buf));
}

static void
p_mark(mark)
    struct sf_strbufmark *mark;
{
    printf("#<strbufmark 0x%lx buf=0x%lx idx=%ld>\n",
           (unsigned long)mark, (unsigned long)mark->buf, mark->idx);
}
#endif
#endif


#ifdef DEBUG
# define D(code) code
#else
# define D(code)
#endif

#ifdef DEBUGB
# define Db(code) if (dodebug) code
#else
# define Db(code)
#endif

#ifdef DEBUGC
# define Dc(code) if (dodebug) code
#else
# define Dc(code)
#endif

#ifdef DEBUGD
# define Dd(code) if (dodebug) code
#else
# define Dd(code)
#endif

#ifdef DEBUGI
# define Di(code) if (dodebug) code
#else
# define Di(code)

#ifdef DEBUGS
# define Ds(code) if (dodebug) code
#else
# define Ds(code)
#endif
#endif

#ifdef DEBUGR
# define Dr(code) if (dodebug) code
#else
# define Dr(code)
#endif


static VALUE
debugon(self)
    VALUE self;
{
    dodebug = 1;
    return Qnil;
}

static VALUE
debugoff(self)
    VALUE self;
{
    dodebug = 0;
    return Qnil;
}


/*
==================================================================
                           util
==================================================================
*/

static unsigned long
sym2iflag(v)
    VALUE v;
{
    ID id;
    unsigned long flag = INSERT_BEFORE;

    if (NIL_P(v))
        return flag;
    if (! SYMBOL_P(v)) {
        rb_raise(rb_eTypeError, "not symbol");
    }
    id = SYM2ID(v);
    if (id == rb_intern("insert_before")) {
        flag = INSERT_BEFORE;
    }
    else if (id == rb_intern("insert_after")) {
        flag = INSERT_AFTER;
    }
    else {
        rb_raise(rb_eArgError, "unknown symbol");
    }
    return flag;
}


/*
==================================================================
                          creation
==================================================================
*/

buffer_t*
defun(buf_new)()
{
    buffer_t *buf;

    buf = ALLOC(buffer_t);
    MEMZERO(buf, buffer_t, 1);

    buf->flags = BUFFER_FLAG;
    buf->ptr = 0;
    buf->len = 0;
    buf->gap = 0;
    buf->point = 0;

    buf->ptr = ALLOC_N(char_t, INITLEN);
    buf->ptr = calloc(sizeof(char), INITLEN);
    buf->len = INITLEN;
    buf->gaplen = INITLEN;

    buf->head = mark_new_com(buf, 0, 0, 1, INSERT_AFTER);
    buf->tail = mark_new_com(buf, 0, 0, 1, INSERT_BEFORE);
    buf->point = mark_new_com(buf, 0, 0, 1, INSERT_BEFORE);

    return buf;
}

void
defun(buf_free)(buf)
    buffer_t *buf;
{
    mark_t *m;

    if (buf->ptr) free(buf->ptr);
    buf->ptr = 0;
    buf->len = 0;
    buf->gap = 0;

    remove_mark(buf->point);
    free(buf->point);
    buf->point = 0;
    for (m = buf->marks; m; m = m->next)
        m->flags &= ~ALIVE_FLAG;
    buf->marks = 0;
    
    free(buf);
}


mark_t*
#ifdef IS_TEXT_BUFFER
defun(mark_new)(buf, idx, cidx, line, flags)
    buffer_t *buf;
    long idx, cidx, line;
    unsigned long flags;
#else
defun(mark_new)(buf, idx, flags)
    buffer_t *buf;
    long idx;
    unsigned long flags;
#endif
{
    mark_t *mark;

    mark = ALLOC(mark_t);
    mark->buf  = buf;
    mark->idx  = idx;
#ifdef IS_TEXT_BUFFER
    mark->cidx = cidx;
    mark->line = line;
#endif
    mark->prev = 0;
    mark->next = 0;

    mark->regi = 0;

    mark->flags = ALIVE_FLAG | BUFFER_FLAG | flags;

    mark->next = buf->marks;
    buf->marks = mark;
    adjust_mark_forward(mark);

    return mark;
}

mark_t*
defun(mark_char_new)(buf, cidx, flags)
    buffer_t *buf;
    long cidx;
    unsigned long flags;
{
#ifdef IS_TEXT_BUFFER
    long i, line;
    i = byte_length(buf, 0, cidx, &line);
    return mark_new(buf, i, cidx, 1 + line, flags);
#else
    return mark_new(buf, cidx, flags);
#endif
}

void
defun(mark_free)(mark)
    mark_t *mark;
{
    if (ALIVE_P(mark))
        remove_mark(mark);
    mark->buf = 0;
    mark->vbuf = Qnil;
    if (mark->regi) {
        re_free_registers(mark->regi);
        mark->regi = 0;
    }
    free(mark);
}



/*
===================================================================
                     byte string handle
===================================================================
*/

long
defun(buf_len)(buf)
    buffer_t *buf;
{
    return LEN(buf);
}

long
defun(buf_clen)(buf)
    buffer_t *buf;
{
    return CLEN(buf);
}


#define REAL_INDEX(buf,i) ((i >= buf->gap) ? i + buf->gaplen : i)
#define PSEUDO_INDEX(buf,i) ((i > buf->gap) ? i - buf->gaplen : i)

#define TB_EACH_CHAR(buf,beg,var) \
do {                                                              \
    long edge = beg >= buf->gap ? buf->len : buf->gap;            \
    var = REAL_INDEX(buf, beg);                                   \
  ec_again:                                                       \
    while (var < edge) {

#define TB_EACH_CHAR_END(buf,var) \
    }                                                             \
    if (edge == buf->gap) {                                       \
        var += buf->gaplen;                                       \
        edge = buf->len;                                          \
        if (var < edge)                                           \
            goto ec_again;                                        \
    }                                                             \
  ec_end:                                                         \
    if (var > buf->gap)                                           \
        var -= buf->gaplen;                                       \
} while (0);

#define TB_EACH_CHAR_REVERSE(buf,beg,var) \
do {                                                              \
    long edge = beg >= buf->gap ? buf->gap + buf->gaplen : 0;      \
    var = REAL_INDEX(buf, beg);                                   \
  ec_again:                                                       \
    while (var > edge) {

#define TB_EACH_CHAR_REVERSE_END(buf,var) \
    }                                                             \
    if (edge != 0) {                                              \
        var -= buf->gaplen;                                       \
        edge = 0;                                                 \
        goto ec_again;                                            \
    }                                                             \
  ec_end:                                                         \
      if (var > buf->gap)                                         \
        var -= buf->gaplen;                                       \
} while (0);

#define TB_BREAK(buf,var) goto ec_end


#define FWDNL(buf, i) do { \
    long edge;                                                            \
    if (i < buf->gap) {                                                   \
        edge = buf->gap;                                                  \
    } else if (i == buf->gap) {                                           \
        i += buf->gaplen;                                                 \
        edge = buf->len;                                                  \
    } else {                                                              \
        edge = buf->len;                                                  \
    }                                                                     \
    if (i != edge) {                                                      \
        i++;                                                              \
        if (buf->ptr[i-1] == '\r' && i != edge && buf->ptr[i] == '\n')    \
            i++;                                                          \
    }                                                                     \
} while (0)

#define BACKNL(buf, i) do { \
    long edge;                                                            \
    if (i == buf->gap + buf->gaplen) {                                    \
        i -= buf->gaplen;                                                 \
        edge = 0;                                                         \
    } else if (i > buf->gap) {                                            \
        edge = buf->gap + buf->gaplen;                                    \
    } else {                                                              \
        edge = 0;                                                         \
    }                                                                     \
    if (i != edge) {                                                      \
        i--;                                                              \
        if (buf->ptr[i] == '\n' && i != edge && buf->ptr[i-1] == '\r')    \
            i--;                                                          \
    }                                                                     \
} while (0)


/* must be called idx is just before NL */
static long
fwdnl(buf, idx)
    buffer_t *buf;
    long idx;
{
    idx = REAL_INDEX(buf, idx);
    FWDNL(buf, idx);
    return PSEUDO_INDEX(buf, idx);
}

/* must be called idx is just after NL */
static long
backnl(buf, idx)
    buffer_t *buf;
    long idx;
{
    idx = REAL_INDEX(buf, idx);
    BACKNL(buf, idx);
    return PSEUDO_INDEX(buf, idx);
}


static long
linehead(buf, idx)
    buffer_t *buf;
    long idx;
{
    long i;
    
    TB_EACH_CHAR_REVERSE(buf, idx, i)
        if (buf->ptr[i-1] == '\n' || buf->ptr[i-1] == '\r')
            TB_BREAK(buf, i);
        i--;
    TB_EACH_CHAR_REVERSE_END(buf, i)
    return i;
}

static long
linetail(buf, idx)
    buffer_t *buf;
    long idx;
{
    long i;

    TB_EACH_CHAR(buf, idx, i)
        if (buf->ptr[i] == '\n' || buf->ptr[i] == '\r')
            TB_BREAK(buf, i);
        i++;
    TB_EACH_CHAR_END(buf, i)
    return i;
}


static long
forward_nline(buf, idx, n)
    buffer_t *buf;
    long idx, n;
{
    long i;

    if (!n) return idx;
    TB_EACH_CHAR(buf, idx, i)
        if (buf->ptr[i] == '\n' || buf->ptr[i] == '\r') {
            FWDNL(buf, i);
            n--;
            if (!n)
                TB_BREAK(buf, i);
        }
        else {
            i++;
        }
    TB_EACH_CHAR_END(buf, i)
    return i;
}

static long
back_nline(buf, idx, n)
    buffer_t *buf;
    long idx, n;
{
    long i;

    if (!n) return idx;
    TB_EACH_CHAR_REVERSE(buf, idx, i)
        if (buf->ptr[i-1] == '\n' || buf->ptr[i-1] == '\r') {
            BACKNL(buf, i);
            n--;
            if (!n)
                TB_BREAK(buf, i);
        }
        else {
            i--;
        }
    TB_EACH_CHAR_REVERSE_END(buf, i)
    return i;
}


#ifdef IS_TEXT_BUFFER
static long
byte_idx(buf, begidx, clen, line)
    buffer_t *buf;
    long begidx, clen;
    long *line;
{
    long i;
    long c = clen;
    long dummy;

    Db(printf("byte_idx> beg=%ld, clen=%ld\n", begidx, clen));
    Db(p_buf(buf));

    if (!line) line = &dummy;
    (*line) = 0;

    TB_EACH_CHAR(buf, begidx, i)
        if (!c)
            TB_BREAK(buf, i);
        switch (buf->ptr[i]) {
        case '\n':
        case '\r':
            FWDNL(buf, i);
            (*line)++;
            break;
        default:
            i += mbclen(buf->ptr[i]);
            break;
        }
        c--;
    TB_EACH_CHAR_END(buf, i)

    Db(printf("byte_idx< clen/len=%ld/%ld\n", clen, i - begidx));
    return i;
}

static long
byte_length(buf, begidx, clen, line)
    buffer_t *buf;
    long begidx, clen;
    long *line;
{
    return byte_idx(buf, begidx, clen, line) - begidx;
}


static long
char_length(buf, begidx, len, line)
    buffer_t *buf;
    long begidx, len;
    long *line;
{
    long i;
    long c = 0;
    long targ;
    long dummy;

    Dc(printf("char_len> beg=%ld, len=%ld\n", begidx, len));
    Dc(p_buf(buf));

    if (!line) line = &dummy;
    *line = 0;
    targ = begidx + len;
    targ = REAL_INDEX(buf, targ);

    TB_EACH_CHAR(buf, begidx, i)
        if (i == targ)
            TB_BREAK(buf, i);
        switch (buf->ptr[i]) {
        case '\n':
        case '\r':
            FWDNL(buf, i);
            (*line)++;
            break;
        default:
            i += mbclen(buf->ptr[i]);
            break;
        }
        c++;
    TB_EACH_CHAR_END(buf, i)

    Dc(printf("char_len< clen=%ld line=%+ld\n", c, *line));
    return c;
}

static long
char_idx(buf, begidx, len, line)
    buffer_t *buf;
    long begidx, len;
    long *line;
{
    return begidx + char_length(buf, begidx, len, line);
}

#else

#define byte_idx(buf, begidx, clen, line) ((begidx) + (clen))
#define byte_length(buf, begidx, clen, line) (clen)
#define char_length(buf, begidx, len, line) (len)
#define char_idx(buf, begidx, len, line) ((begidx) + (len))
#endif


static long
fwdcol(buf, beg, clen)
    buffer_t *buf;
    long beg, clen;
{
    long i;
    long c = clen;

    Db(printf("fwdcol> beg=%ld, clen=%ld\n", beg, clen));
    Db(p_buf(buf));

    TB_EACH_CHAR(buf, beg, i)
        if (!c)
            TB_BREAK(buf, i);
        switch (buf->ptr[i]) {
        case '\n':
        case '\r':
            TB_BREAK(buf, i);
            break;
        default:
            i += mbclen(buf->ptr[i]);
            break;
        }
        c--;
    TB_EACH_CHAR_END(buf, i)

    Db(printf("fwdcol< clen/len=%ld/%ld\n", clen, i - beg));
    return i;
}


static void
setgap(buf, idx)
    buffer_t *buf;
    long idx;
{
    if (idx < buf->gap) {
        memmove(buf->ptr + (idx + buf->gaplen),
                buf->ptr + idx,
                buf->gap - idx);
    }
    else {
        memmove(buf->ptr + buf->gap,
                buf->ptr + (buf->gap + buf->gaplen),
                idx - buf->gap);
    }
    buf->gap = idx;
}


#define ISIZE 1024

extern int reall;
static void
insert(buf, idx, str, len)
    buffer_t *buf;
    long idx;
    char_t *str;
    long len;
{
    CRITICAL_CHECK(buf);

    if (buf->gaplen < len) {
        long newcapa, inc;
        
        if (len < ISIZE)
            inc = ISIZE;
        else
            inc = len * 2;
        newcapa = buf->len + inc;
        REALLOC_N(buf->ptr, char_t, newcapa);

        if (idx < buf->gap) {
            long pl1, pl2;
            pl1 = buf->len - buf->gap - buf->gaplen;
            memmove(buf->ptr + (newcapa - pl1),
                    buf->ptr + (buf->gap + buf->gaplen),
                    pl1);
            pl2 = buf->gap - idx;
            memmove(buf->ptr + (newcapa - pl1 - pl2),
                    buf->ptr + idx,
                    pl2);
        }
        else {
            long pl1, pl2;
            pl1 = buf->len - (idx + buf->gaplen);
            memmove(buf->ptr + (newcapa - pl1),
                    buf->ptr + (buf->len - pl1),
                    pl1);
            pl2 = idx - buf->gap;
            memmove(buf->ptr + buf->gap,
                    buf->ptr + buf->gap + buf->gaplen,
                    pl2);
        }
        buf->len = newcapa;
        buf->gap = idx;
        buf->gaplen += inc;
    }
    else {
        setgap(buf, idx);
    }
    memmove(buf->ptr + idx,
            str,
            sizeof(char_t) * len);
    buf->gap += len;
    buf->gaplen -= len;
}


static void
delete(buf, idx, len)
    buffer_t *buf;
    long idx, len;
{
    Dd(printf("\ndelete\n"));
    Dd(p_buf(buf));
    Dd(printf("idx=%ld, len=%ld\n", idx, len));

    CRITICAL_CHECK(buf);

    if (idx < buf->gap) {
        if (idx + len > buf->gap) {
            /*
                 OOOOOxxxx-----xxOOOO

                 OOOOO-----------OOOO
            */
            buf->gap = idx;
            buf->gaplen += len;
        }
        else {
            /*
                 OOOOOxxxxxxXXX----OOOOOO

                 OOOOO----------XXXOOOOOO
            */
            memmove(buf->ptr + (idx + len) + buf->gaplen,
                    buf->ptr + (idx + len),
                    buf->gap - (idx + len));
            buf->gap = idx;
            buf->gaplen += len;
        }
    }
    else {
        /*
             OOOOO---XXxxxOOOO

             OOOOOXX------OOOO
        */
        long pl = idx - buf->gap;

        memmove(buf->ptr + buf->gap,
                buf->ptr + buf->gap + buf->gaplen,
                pl);
        buf->gap += pl;
        buf->gaplen += len;
    }
}


static VALUE
substr(buf, idx, len)
    buffer_t *buf;
    long idx, len;
{
    Ds(printf("substr: idx=%ld, len=%ld\n", idx, len));
    if ((idx <= buf->gap) && (idx + len > buf->gap)) {
        VALUE ret;
        Ds(printf("1=%ld 2=%ld\n", buf->gap - idx, idx + len - buf->gap));
        ret = rb_str_new(buf->ptr + idx, len);
        memcpy(RSTRING(ret)->ptr + (buf->gap - idx),
               buf->ptr + buf->gap + buf->gaplen,
               idx + len - buf->gap);
        return ret;
    }
    else {
        Ds(printf("1=%ld\n", len));
        return rb_str_new(buf->ptr + REAL_INDEX(buf, idx), len);
    }
}


static VALUE
getline(buf, idx)
    buffer_t *buf;
    long idx;
{
    long head, tail;

    head = linehead(buf, idx);
    tail = linetail(buf, idx);
    if (head == LEN(buf))
        return Qnil;
    return substr(buf, head, tail - head);
}

static void
each_line(buf, beg)
    buffer_t *buf;
    long beg;
{
    long head, tail;

    if (LEN(buf) == 0) {
        rb_yield(rb_str_new("", 0));
        return;
    }

    head = linehead(buf, beg);
    while (head < LEN(buf)) {
        tail = linetail(buf, head);
        tail = fwdnl(buf, tail);
        rb_yield(substr(buf, head, tail - head));
        head = tail;
    }
}

static void
r_each_line(buf, beg)
    buffer_t *buf;
    long beg;
{
    long head, tail;

    if (LEN(buf) == 0) {
        rb_yield(rb_str_new("", 0));
        return;
    }

    tail = linetail(buf, beg);
    tail = fwdnl(buf, tail);
    while (tail > 0) {
        head = backnl(buf, tail);
        head = linehead(buf, head);
        rb_yield(substr(buf, head, tail - head));
        tail = head;
    }
}


struct roextr_param {
    buffer_t *buf;
    long idx;
    long len;
    struct RString *str;
};

static VALUE
roeach_i(arg)
    VALUE arg;
{
    struct roextr_param *p = (struct roextr_param*)arg;
    buffer_t *buf = p->buf;
    struct RString *str = p->str;
    long head, tail;

    head = p->idx;
    while (head < LEN(buf)) {
        tail = linetail(buf, head);
        tail = fwdnl(buf, tail);
        if (head < buf->gap && tail > buf->gap)
            setgap(buf, tail);
        str->ptr = buf->ptr + REAL_INDEX(buf, head);
        str->len = tail - head;
        rb_yield((VALUE)str);
        head = tail;
    }
    return Qnil;
}

static VALUE
roextr_ensure(arg)
    VALUE arg;
{
    struct roextr_param *p = (struct roextr_param*)arg;

    RSTRING(p->str)->ptr = 0;
    RSTRING(p->str)->len = 0;
    CRITICAL_END(p->buf);
    return Qnil;
}

static void
each_line_ro(buf, idx)
    buffer_t *buf;
    long idx;
{
    struct roextr_param p;

    NEWOBJ(str, struct RString);
    OBJSETUP(str, rb_cString, T_STRING);
    str->ptr = 0;
    str->len = 0;
    str->orig = 0;
    OBJ_FREEZE(str);

    CRITICAL_BEGIN(buf);
    p.str = str;
    p.buf = buf;
    p.idx = idx;
    p.len = 0;
    rb_ensure(roeach_i, (VALUE)&p, roextr_ensure, (VALUE)&p);
}


static VALUE
rosubstr_i(arg)
    VALUE arg;
{
    struct roextr_param *p = (struct roextr_param*)arg;
    buffer_t *buf = p->buf;
    struct RString *str = p->str;
    long idx = p->idx;
    long len = p->len;

    if (idx <= buf->gap && idx + len > buf->gap)
        setgap(buf, idx + len);
    str->ptr = buf->ptr + idx;
    str->len = len;
    rb_yield((VALUE)str);
    return Qnil;
}

static void
substr_ro(buf, idx, len)
    buffer_t *buf;
    long idx, len;
{
    struct roextr_param p;

    NEWOBJ(str, struct RString);
    OBJSETUP(str, rb_cString, T_STRING);
    str->ptr = 0;
    str->len = 0;
    str->orig = 0;
    OBJ_FREEZE(str);

    CRITICAL_BEGIN(buf);
    p.str = str;
    p.buf = buf;
    p.idx = idx;
    p.len = len;
    rb_ensure(rosubstr_i, (VALUE)&p, roextr_ensure, (VALUE)&p);
}


#define PART_BEFORE_GAP(retcode, nextch, edgeptr, OP) \
    while (p OP pend) {                                         \
        if (*p == ch) {                                         \
            if (p + slen > edgeptr) {                           \
                long sl = edgeptr - p;                          \
                if (p - buf->ptr + slen > LEN(buf))             \
                    return -1;                                  \
                if (memcmp(p, sub, sl) == 0 &&                  \
                    memcmp(redge, sub + sl, slen - sl) == 0) {  \
                    retcode;                                    \
                }                                               \
            }                                                   \
            else {                                              \
                if (memcmp(p, sub, slen) == 0) {                \
                    retcode;                                    \
                }                                               \
            }                                                   \
        }                                                       \
        nextch;                                                 \
    }

#define PART_AFTER_GAP(retcode, nextch, OP) \
    while (p OP pend) {                                         \
        if (*p == ch && memcmp(p, sub, slen))                   \
            retcode;                                            \
        nextch;                                                 \
    }

#define SEARCH_FUNCTION(inccode, retcode1, retcode2) \
    char_t *p, *pend, *redge;                                   \
    char_t ch;                                                    \
                                                                \
    if (slen == 0) return idx;                                  \
    ch = sub[0];                                                \
    redge = buf->ptr + buf->gap + buf->gaplen;                  \
                                                                \
    p = buf->ptr + idx;                                         \
    pend = buf->ptr + buf->gap - slen;                          \
    Dr(printf("search (before gap) i=%ld end=%ld r-edge=%ld\n", \
       p - buf->ptr, pend - buf->ptr, redge - buf->ptr));       \
    PART_BEFORE_GAP(retcode1, inccode, pend, <)                 \
                                                                \
    p += buf->gaplen;                                           \
    pend = buf->ptr + buf->len - slen;                          \
    Dr(printf("search (after gap) i=%ld end=%ld\n",             \
       p    - buf->ptr - buf->gaplen,                           \
       pend - buf->ptr - buf->gaplen));                         \
    PART_AFTER_GAP(retcode2, inccode, <)                        \
    \
    return -1;


#define SEARCH_R_FUNCTION(deccode, retcode1, retcode2) \
    char_t *p, *pend, *gapp, *redge;                            \
    char_t ch;                                                  \
                                                                \
    if (slen == 0) return 0;                                    \
    ch = sub[0];                                                \
    gapp = buf->ptr + buf->gap;                                 \
    redge = gapp + buf->gaplen;                                 \
                                                                \
    p = buf->ptr + idx + buf->gaplen;                           \
    pend = redge;                                               \
    if (idx + slen <= LEN(buf))                                 \
        PART_AFTER_GAP(retcode2, deccode, >=)                   \
                                                                \
    p -= buf->gaplen;                                           \
    pend = buf->ptr;                                            \
    if (idx + slen > LEN(buf))                                  \
        p -= idx + slen - LEN(buf);                             \
    PART_BEFORE_GAP(retcode1, deccode, gapp, >=)                \
    \
    return -1;


#ifdef IS_TEXT_BUFFER

/* return offset (char) from "idx" (char) */
static long
buf_index(buf, sub, slen, idx, range)
    buffer_t *buf;
    char *sub;
    long slen, idx, range;
{
    long offset = idx;
    idx = byte_idx(buf, 0, offset, 0); /* idx: byte, offset: char */

    if (1) {
    SEARCH_FUNCTION(
        (p += mbclen(*p), offset++),
        return offset,
        return offset)
    }
}

static int
charbound_p(buf, p)
    buffer_t *buf;
    char *p;
{
    mark_t *m;
    long idx = p - buf->ptr;
    long i, upp;

    for (m = buf->marks; m->next; m = m->next) {
        if (m->next->idx > idx)
            break;
    }
    upp = REAL_INDEX(buf, idx);
    TB_EACH_CHAR(buf, m->idx, i)
        if (i >= upp)
            TB_BREAK(buf, i);
        switch (buf->ptr[i]) {
        case '\n':
        case '\r':
            FWDNL(buf, i);
            break;
        default:
            i += mbclen(buf->ptr[i]);
            break;
        }
    TB_EACH_CHAR_END(buf, i)

    return i == idx ? 1 : 0;
}

/* return offset (char) from "idx" (byte) */
static long
buf_rindex(buf, sub, slen, idx, range)
    buffer_t *buf;
    char_t *sub;
    long slen, idx, range;
{
    long offset = idx;
    idx = byte_idx(buf, 0, idx, 0);

    if (1) {
    SEARCH_R_FUNCTION(
        (p -= mbclen(*p), offset++),
        if (charbound_p(buf, p)) return offset,
        if (charbound_p(buf, p)) return offset)
    }
}

#else

static long
buf_index(buf, sub, slen, idx, range)
    buffer_t *buf;
    char_t *sub;
    long slen, idx, range;
{
    SEARCH_FUNCTION(
        p++,
        return p - buf->ptr,
        return p - buf->ptr - buf->gaplen)
}

static long
buf_rindex(buf, sub, slen, idx, range)
    buffer_t *buf;
    char_t *sub;
    long slen, idx, range;
{
    SEARCH_R_FUNCTION(
        p--,
        return p - buf->ptr,
        return p - buf->ptr - buf->gaplen)
}

#endif

static long
buf_reg_search(buf, re, pos, range, regi)
    buffer_t *buf;
    struct re_pattern_buffer *re;
    long pos, range;
    struct re_registers *regi;
{
    long ret;

    if (buf->gap > pos && buf->gap + buf->gaplen < LEN(buf))
        setgap(buf, LEN(buf));
    Dr(printf("pos=%ld, len=%ld, range=%ld\n", pos, LEN(buf)-pos, range));
    ret = ruby_re_search(re,
                         buf->ptr + pos,
                         LEN(buf) - pos,
                         0, range, regi);
    if (ret == -2)
        rb_raise(rb_eRuntimeError, "regexp stack overflow");
    if (ret >= 0)
        ret += pos;
    if (regi) {
        int i;
        for (i = 0; i < regi->num_regs; i++) {
          regi->beg[i] += pos;
          regi->end[i] += pos;
        }
    }
            
    return ret;
}

static VALUE
bufregi_new(p)
    struct re_registers *p;
{
    ruby_re_free_registers(p);
    return Qnil;
}

static long
buf_search(buf, vre, pos, range, rev, regi)
    buffer_t *buf;
    VALUE vre;
    long pos, range;
    int rev;
    struct re_registers **regi;
{
    struct re_pattern_buffer *re;
    struct re_registers *rp;
    long ret;
    long (*func)();
    int alloc = 0;

    switch (TYPE(vre)) {
    case T_REGEXP:
    regextype:
        re = RREGEXP(vre)->ptr;

        if (regi) {
            if (!*regi) {
                rp = *regi;
            }
            else {
                rp = ALLOC(struct re_registers);
                memset(rp, 0, sizeof(struct re_registers));
                alloc = 1;
            }
        }
        else {
            rp = 0;
        }
        ret = buf_reg_search(buf, re, pos, range, rp);
        if (regi && alloc) {
            if (ret < 0) {
                ruby_re_free_registers(rp);
                rp = 0;
            }
            *regi = rp;
        }
        return ret;

    case T_STRING:
    strtype:
        if (regi) {
            vre = rb_reg_regcomp(vre);
            goto regextype;
        }
        if (rev) {
            range = -range;
            func = buf_rindex;
        }
        else {
            func = buf_index;
        }
        return (*func)(buf, RSTRING(vre)->ptr, RSTRING(vre)->len, pos, range);

    default:
        vre = rb_String(vre);
        goto strtype;
    }
}



/*
==================================================================
                      mark list handle
==================================================================
*/

static void
insert_mark_between(mark, prev, next)
    mark_t *mark, *prev, *next;
{
    mark->prev = prev;
    mark->next = next;
    if (prev) prev->next = mark;
    if (next) next->prev = mark;
}

static void
remove_mark(mark)
    mark_t *mark;
{
    if (mark->prev)
        mark->prev->next = mark->next;
    if (mark->next)
        mark->next->prev = mark->prev;
    mark->prev = 0;
    mark->next = 0;
}

static mark_t*
first_mark(mark)
    mark_t *mark;
{
    mark_t *top;

    top = mark->buf->marks ? mark->buf->marks : mark;
    while (top->prev)
        top = top->prev;
    
    return top;
}

static void
adjust_mark_forward(mark)
    mark_t *mark;
{
    mark_t *prev, *next;

    prev = mark->prev;
    next = mark->next;
    remove_mark(mark);

    while (next) {
        if (mark->idx == next->idx) {
            if ((mark->flags & INSERT_BEFORE) <=
                (next->flags & INSERT_BEFORE))
                /* ins-after is "little" than ins-before */
                break;
        }
        else if (mark->idx < next->idx) {
            /* if ((prev && prev->idx < mark->idx) || !prev) */
            break;
        }
        prev = next;
        next = next->next;
    }
    insert_mark_between(mark, prev, next);

    mark->buf->marks = first_mark(mark);
}

static void
adjust_mark_backward(mark)
    mark_t *mark;
{
    mark_t *top, *prev, *next;

    prev = mark->prev;
    next = mark->next;
    remove_mark(mark);

    while (prev) {
        if (prev->idx == mark->idx) {
            if ((prev->flags & INSERT_BEFORE) <=
                (mark->flags & INSERT_BEFORE))
                /* ins-after is "little" than ins-before */
                break;
        }
        else if (prev->idx < mark->idx) {
            /* if ((next && mark->idx < next->idx) || !next) */
            break;
        }
        next = prev;
        prev = prev->prev;
    }

    insert_mark_between(mark, prev, next);

    top = mark->buf->marks ? mark->buf->marks : mark;
    while (top->prev)
        top = top->prev;
    mark->buf->marks = top;
}



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


#define linehead_m(m) linehead(m->buf, m->idx)
#define linetail_m(m) linetail(m->buf, m->idx)

#ifdef IS_TEXT_BUFFER
static void
mark_forward_char(mark, clen)
    mark_t *mark;
    long clen;
{
    mark_t *m;
    long targ;
    long line;

    MARK_ASSERT(clen, mark->cidx + clen > CLEN(mark->buf), "fwd_char");
    targ = mark->cidx + clen;
    for (m = mark; m->next; m = m->next) {
        if (m->next->cidx > targ)
            break;
    }
    mark->idx = byte_idx(mark->buf, m->idx, clen, &line);
    mark->cidx = targ;
    mark->line += line;

    adjust_mark_forward(mark);
}
#else
# define mark_forward_char(m,l) mark_forward_byte(m,l)
#endif

static void
mark_forward_byte(mark, len)
    mark_t *mark;
    long len;
{
L(  long line;   )

    MARK_ASSERT(len, mark->idx + len > LEN(mark->buf), "fwd_byte");
C(  mark->cidx +=                                        )
L(      char_length(mark->buf, mark->idx, len, &line);   )
L(  mark->line += line;                                  )
    mark->idx += len;   /* DO NOT MOVE */

    adjust_mark_forward(mark);
}

#ifdef IS_TEXT_BUFFER
static void
mark_back_char(mark, clen)
    mark_t *mark;
    long clen;
{
    mark_t *m;
    long targ, i, cl, line;

    MARK_ASSERT(clen, clen > mark->cidx, "back_char");
    targ = mark->cidx - clen;
    for (m = mark; m->prev; m = m->prev) {
        if (m->prev->cidx < targ) {
            m = m->prev;
            break;
        }
    }
    if (m->cidx < targ) {
        i = m->idx;
        cl = targ - m->cidx;
    }
    else {
        m = 0;
        i = 0;
        cl = targ;
    }
    mark->idx = byte_idx(mark->buf, i, cl, &line);
    mark->cidx = targ;
    mark->line = m ? m->line + line : 1 + line;

    adjust_mark_backward(mark);
}
#else
# define mark_back_char(m,l) mark_back_byte(m,l)
#endif

static void
mark_back_byte(mark, len)
    mark_t *mark;
    long len;
{
L(  long line;   )

    MARK_ASSERT(len, len > mark->idx, "back_byte");
    mark->idx -= len;
C(  mark->cidx -=                                        )
L(      char_length(mark->buf, mark->idx, len, &line);   )
L(  mark->line -= line;                                  )

    adjust_mark_backward(mark);
}

void
defun(mark_move)(mark, len)
    mark_t *mark;
    long len;
{
    if (len >= 0) {
        CRAMP_CLEN(mark->buf, CIDX(mark), len);
        mark_forward_char(mark, len);
    }
    else {
        CRAMP_CLEN_NEG(mark->buf, CIDX(mark), len);
        mark_back_char(mark, len);
    }
}


void
defun(mark_moveto)(mark, m)
    mark_t *mark, *m;
{
    long i;

    i = mark->idx;
    mark->idx = m->idx;
C(  mark->cidx = m->cidx;   )
L(  mark->line = m->line;   )

    if (i > m->idx)
        adjust_mark_backward(mark);
    else
        adjust_mark_forward(mark);
}

void
defun(mark_goto)(mark, i)
    mark_t *mark;
    long i;
{
    mark_move(mark, i - CIDX(mark));
}


void
defun(mark_nextline)(mark)
    mark_t *mark;
{
    long i;

    i = linetail_m(mark);
    if (i < mark->buf->len) {
        i = fwdnl(mark->buf, i);
    }
    mark_forward_byte(mark, i - mark->idx);
}

void
defun(mark_prevline)(mark)
    mark_t *mark;
{
    long i;

    i = linehead_m(mark);
    if (i > 0) {
        i = backnl(mark->buf, i);
    }
    mark_back_byte(mark, mark->idx - i);
}


void
defun(mark_forward_nline)(mark, n)
    mark_t *mark;
    long n;
{
    long i;

    i = forward_nline(mark->buf, mark->idx, n);
    mark_forward_byte(mark, i - mark->idx);
}

void
defun(mark_back_nline)(mark, n)
    mark_t *mark;
    long n;
{
    long i;

    i = back_nline(mark->buf, mark->idx, n);
    mark_back_byte(mark, mark->idx - i);
}


void
defun(mark_linetail)(mark)
    mark_t *mark;
{
    long i;

    i = linetail_m(mark);
    mark_forward_byte(mark, i - mark->idx);
}

void
defun(mark_linehead)(mark)
    mark_t *mark;
{
    long i;

    i = linehead_m(mark);
    mark_back_byte(mark, mark->idx - i);
}


long
defun(mark_col)(mark)
    mark_t *mark;
{
    long i;

    i = linehead_m(mark);
    return char_length(mark->buf, i, IDX(mark) - i, 0);
}

long
defun(mark_bcol)(mark)
    mark_t *mark;
{
    return IDX(mark) - linehead_m(mark);
}

void
defun(mark_setcol)(mark, i)
    mark_t *mark;
    long i;
{
    long head, orig;

    orig = mark->idx;
    head = linehead_m(mark);
    i = fwdcol(mark->buf, head, i);
    mark->idx = i;
C(  mark->cidx = char_idx(mark->buf, head, i - head, 0);   )
    if (i > orig)
        adjust_mark_forward(mark);
    else
        adjust_mark_backward(mark);
}


void
defun(mark_insert)(mark, str, len)
    mark_t *mark;
    char *str;
    long len;
{
    mark_t *m;
    long pnt = mark->idx;   /* We must save this
                               because mark->idx will be changed */
C(  long clen;   )
L(  long line;   )

    insert(mark->buf, mark->idx, str, len);
L(  clen = char_length(mark->buf, mark->idx, len, &line);   )

    for (m = mark; m->prev; m = m->prev) {
        if (m->prev->idx != m->idx)
            break;
    }
    for (; m && m->idx == pnt; m = m->next) {
        if (m->flags & INSERT_BEFORE) {
            m->idx += len;
C(          m->cidx += clen;   )
L(          m->line += line;   )
        }
    }
    for (; m; m = m->next) {
        m->idx += len;
C(      m->cidx += clen;   )
L(      m->line += line;   )
    }
}


/* -L <= len <= L, len is byte */
void
defun(mark_delete)(mark, len)
    mark_t *mark;
    long len;
{
    mark_t *m;
    long pnt = mark->idx;
    long upper = mark->idx + len;
C(  long clen;   )
L(  long line;   )

    MARK_ASSERT(len, upper > LEN(mark->buf), "delete");
L(  clen = char_length(mark->buf, pnt, len, &line);   )
    delete(mark->buf, pnt, len);

    for (m = mark; m->prev; m = m->prev) {
        if (m->prev->idx != m->idx)
            break;
    }
    for (; m && m->idx < upper; m = m->next) {
        m->idx = pnt;
C(      m->cidx = mark->cidx;   )
L(      m->line = mark->line;   )
    }
    for (; m; m = m->next) {
        m->idx -= len;
C(      m->cidx -= clen;   )
L(      m->line -= line;   )
    }
}
