module std::io;

struct Scanner (InStream)
{
	InStream wrapped_stream;
	char[] buf;
	usz pattern_idx;
	usz read_idx;
}

<*
 Scanner provides a way to read delimited data (with newlines as the default).
 The supplied buffer must be at least as large as the expected data length
 including its pattern.

 @param [&in] stream : "The stream to read data from."
 @require buffer.len > 0 : "Non-empty buffer required."
*>
fn void Scanner.init(&self, InStream stream, char[] buffer)
{
	*self = { .wrapped_stream = stream, .buf = buffer };
}


<*
 Return and clear any remaining unscanned data.
*>
fn char[] Scanner.flush(&self) @dynamic
{
	assert(self.read_idx >= self.pattern_idx);
	usz n = self.read_idx - self.pattern_idx;
	char[] buf = self.buf[self.pattern_idx:n];
	self.pattern_idx = 0;
	self.read_idx = 0;
	return buf;
}

fn void? Scanner.close(&self) @dynamic
{
	if (&self.wrapped_stream.close) return self.wrapped_stream.close();
}

<*
 Scan the stream for the next split character and return data up to the match.
 @require pattern.len > 0 : "Non-empty pattern required."
 @require self.buf.len > pattern.len : "Pattern too large."
*>
fn char[]? Scanner.scan(&self, String pattern = "\n")
{
	if (self.read_idx == 0)
	{
		// First read.
		self.read_idx = self.refill(self.buf)!;
		self.pattern_idx = 0;
	}
	assert(self.read_idx >= self.pattern_idx);
	usz n = self.read_idx - self.pattern_idx;
	char[] buf = self.buf[self.pattern_idx:n];
	if (try i = self.find(buf, pattern))
	{
		self.pattern_idx += i + pattern.len;
		return buf[:i];
	}
	if (self.pattern_idx == 0 || self.read_idx < self.buf.len)
	{
		// Split pattern not found with maximized search, abort.
		// Split pattern not found and already read as much as possible.
		return NOT_FOUND?;
	}
	// Split pattern not found: maximize the search and try one more time.
	self.buf[:n] = buf[..];
	self.pattern_idx = 0;

	buf = self.buf[n..];
	usz p = self.refill(buf)!;
	self.read_idx = n + p;

	buf = buf[:p];
	usz i = self.find(buf, pattern)!;
	self.pattern_idx = n + i + pattern.len;

	return self.buf[:n + i];
}

macro usz? Scanner.find(&self, buf, pattern) @private
{
	return ((String)buf).index_of(pattern);
}

macro usz? Scanner.refill(&self, buf) @private
{
	usz? n = self.wrapped_stream.read(buf);
	if (catch err = n)
	{
		if (err == io::EOF) return NOT_FOUND?;
		return err?;
	}
	return n;
}

fn usz? Scanner.read(&self, char[] bytes) @dynamic
{
	usz n;
	if (self.pattern_idx < self.read_idx)
	{
		n = min(bytes.len, self.read_idx - self.pattern_idx);
		bytes[:n] = self.buf[self.pattern_idx:n];
		self.pattern_idx += n;
		bytes = bytes[n..];
	}
	n += self.wrapped_stream.read(bytes)!;
	return n;
}

fn char? Scanner.read_byte(&self) @dynamic
{
	if (self.pattern_idx < self.read_idx)
	{
		return self.buf[self.pattern_idx++];
	}
	return self.wrapped_stream.read_byte();
}
