#ifndef PEEK_FILE_READER_H
#define PEEK_FILE_READER_H

#include <stdio.h>
#include "lib/mlrutil.h"
#include "lib/mlrmath.h"
#include "lib/mlr_globals.h"
#include "input/byte_reader.h"

// This is a ring-buffered peekahead file/string reader.

// Note: Throughout Miller as a general rule I treat struct attributes as if
// they were private attributes. However, for performance, parse_trie_ring_match
// accesses this ring buffer directly.
typedef struct _peek_file_reader_t {
	byte_reader_t* pbr;
	int   peekbuflen;
	int   peekbuflenmask;
	char* peekbuf;
	int   sob; // start of ring-buffer
	int   npeeked;
} peek_file_reader_t;

// ----------------------------------------------------------------
static inline peek_file_reader_t* pfr_alloc(byte_reader_t* pbr, int maxnpeek) {
	peek_file_reader_t* pfr = mlr_malloc_or_die(sizeof(peek_file_reader_t));
	pfr->pbr            = pbr;
	pfr->peekbuflen     = power_of_two_above(maxnpeek);
	pfr->peekbuflenmask = pfr->peekbuflen - 1;
	// The peek-buffer doesn't contain null-terminated C strings, but we
	// nonetheless null-terminate the buffer with an extra never-touched byte
	// so that print statements in the debugger, etc. will be nice.
	pfr->peekbuf        =  mlr_malloc_or_die(pfr->peekbuflen + 1);
	memset(pfr->peekbuf, 0, pfr->peekbuflen + 1);
	pfr->sob            =  0;
	pfr->npeeked        =  0;

	return pfr;
}

// ----------------------------------------------------------------
static inline void pfr_free(peek_file_reader_t* pfr) {
	if (pfr == NULL)
		return;
	free(pfr->peekbuf);
	free(pfr);
}

// ----------------------------------------------------------------
static inline void pfr_reset(peek_file_reader_t* pfr) {
	memset(pfr->peekbuf, 0, pfr->peekbuflen);
	pfr->sob     = 0;
	pfr->npeeked = 0;
}

// ----------------------------------------------------------------
static inline char pfr_peek_char(peek_file_reader_t* pfr) {
	if (pfr->npeeked < 1) {
		pfr->peekbuf[pfr->sob] = pfr->pbr->pread_func(pfr->pbr);
		pfr->npeeked++;
	}
	return pfr->peekbuf[pfr->sob];
}

// ----------------------------------------------------------------
static inline char pfr_read_char(peek_file_reader_t* pfr) {
	if (pfr->npeeked < 1) {
		return pfr->pbr->pread_func(pfr->pbr);
	} else {
		char c = pfr->peekbuf[pfr->sob];
		pfr->sob = (pfr->sob + 1) & pfr->peekbuflenmask;
		pfr->npeeked--;
		return c;
	}
}

// ----------------------------------------------------------------
static inline void pfr_buffer_by(peek_file_reader_t* pfr, int len) {
	while (pfr->npeeked < len) {
		pfr->peekbuf[(pfr->sob + pfr->npeeked++) & pfr->peekbuflenmask] = pfr->pbr->pread_func(pfr->pbr);
	}
}

// ----------------------------------------------------------------
static inline void pfr_advance_by(peek_file_reader_t* pfr, int len) {
	MLR_INTERNAL_CODING_ERROR_IF(len > pfr->npeeked);
	pfr->sob = (pfr->sob + len) & pfr->peekbuflenmask;
	pfr->npeeked -= len;
}

// ----------------------------------------------------------------
void pfr_print(peek_file_reader_t* pfr);

#endif // PEEK_FILE_READER_H
