/* sax_buf.h
 * Copyright (c) 2011, Peter Ohler
 * All rights reserved.
 */

#ifndef OX_SAX_BUF_H
#define OX_SAX_BUF_H

#include <stdio.h>

typedef struct _buf {
    char	base[0x00001000];
    char	*head;
    char	*end;
    char	*tail;
    char	*read_end;      /* one past last character read */
    char       	*pro;           /* protection start, buffer can not slide past this point */
    char        *str;           /* start of current string being read */
    off_t	pos;
    off_t	line;
    off_t	col;
    off_t	pro_pos;
    off_t	pro_line;
    off_t	pro_col;
    int		(*read_func)(struct _buf *buf);
    union {
        int     	fd;
        VALUE   	io;
	const char	*str;
    } in;
    struct _saxDrive	*dr;
} *Buf;

typedef struct _checkPt {
    off_t	pro_dif;
    off_t	pos;
    off_t	line;
    off_t	col;
    char	c;
} *CheckPt;

#define CHECK_PT_INIT { -1, 0, 0, 0, '\0' }

extern void	ox_sax_buf_init(Buf buf, VALUE io);
extern int	ox_sax_buf_read(Buf buf);

static inline char
buf_get(Buf buf) {
    //printf("*** drive get from '%s'  from start: %ld  buf: %p  from read_end: %ld\n", buf->tail, buf->tail - buf->head, buf->head, buf->read_end - buf->tail);
    if (buf->read_end <= buf->tail) {
        if (0 != ox_sax_buf_read(buf)) {
            return '\0';
        }
    }
    if ('\n' == *buf->tail) {
        buf->line++;
        buf->col = 0;
    } else {
	buf->col++;
    }
    buf->pos++;

    return *buf->tail++;
}

static inline void
buf_backup(Buf buf) {
    buf->tail--;
    buf->col--;
    buf->pos--;
    if (0 >= buf->col) {
	buf->line--;
	// allow col to be negative since we never backup twice in a row
    }
}

static inline void
buf_protect(Buf buf) {
    buf->pro = buf->tail;
    buf->str = buf->tail; // can't have str before pro
    buf->pro_pos = buf->pos;
    buf->pro_line = buf->line;
    buf->pro_col = buf->col;
}

static inline void
buf_reset(Buf buf) {
    buf->tail = buf->pro;
    buf->pos = buf->pro_pos;
    buf->line = buf->pro_line;
    buf->col = buf->pro_col;
}

/* Starts by reading a character so it is safe to use with an empty or
 * compacted buffer.
 */
static inline char
buf_next_non_white(Buf buf) {
    char        c;

    while ('\0' != (c = buf_get(buf))) {
	switch(c) {
	case ' ':
	case '\t':
	case '\f':
	case '\n':
	case '\r':
	    break;
	default:
	    return c;
	}
    }
    return '\0';
}

/* Starts by reading a character so it is safe to use with an empty or
 * compacted buffer.
 */
static inline char
buf_next_white(Buf buf) {
    char        c;

    while ('\0' != (c = buf_get(buf))) {
	switch(c) {
	case ' ':
	case '\t':
	case '\f':
	case '\n':
	case '\r':
	case '\0':
	    return c;
	default:
	    break;
	}
    }
    return '\0';
}

static inline void
buf_cleanup(Buf buf) {
    if (buf->base != buf->head && 0 != buf->head) {
        xfree(buf->head);
	buf->head = 0;
    }
}

static inline int
is_white(char c) {
    switch(c) {
    case ' ':
    case '\t':
    case '\f':
    case '\n':
    case '\r':
        return 1;
    default:
        break;
    }
    return 0;
}

static inline void
buf_checkpoint(Buf buf, CheckPt cp) {
    cp->pro_dif = (int)(buf->tail - buf->pro);
    cp->pos = buf->pos;
    cp->line = buf->line;
    cp->col = buf->col;
    cp->c = *(buf->tail - 1);
}

static inline int
buf_checkset(CheckPt cp) {
    return (0 <= cp->pro_dif);
}

static inline char
buf_checkback(Buf buf, CheckPt cp) {
    buf->tail = buf->pro + cp->pro_dif;
    buf->pos = cp->pos;
    buf->line = cp->line;
    buf->col = cp->col;
    return cp->c;
}

static inline void
buf_collapse_return(char *str) {
    char	*s = str;
    char	*back = str;

    for (; '\0' != *s; s++) {
	if (back != str && '\n' == *s && '\r' == *(back - 1)) {
	    *(back - 1) = '\n';
	} else {
	    *back++ = *s;
	}
    }
    *back = '\0';
}

static inline void
buf_collapse_white(char *str) {
    char	*s = str;
    char	*back = str;

    for (; '\0' != *s; s++) {
	switch(*s) {
	case ' ':
	case '\t':
	case '\f':
	case '\n':
	case '\r':
	    if (back == str || ' ' != *(back - 1)) {
		*back++ = ' ';
	    }
	    break;
	default:
	    *back++ = *s;
	    break;
	}
    }
    *back = '\0';
}

#endif /* OX_SAX_BUF_H */
