package bufio

import "core:bytes"
import "core:io"
import "core:mem"
import "core:unicode/utf8"
import "intrinsics"

// Extra errors returns by scanning procedures
Scanner_Extra_Error :: enum i32 {
	Negative_Advance,
	Advanced_Too_Far,
	Bad_Read_Count,
	Too_Long,
	Too_Short,
}

Scanner_Error :: union {
	io.Error,
	Scanner_Extra_Error,
}

// Split_Proc is the signature of the split procedure used to tokenize the input.
Split_Proc :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool);

Scanner :: struct {
	r:              io.Reader,
	split:          Split_Proc,

	buf:            [dynamic]byte,
	max_token_size: int,
	start:          int,
	end:            int,
	token:          []byte,

	_err: Scanner_Error,
	max_consecutive_empty_reads:  int,
	successive_empty_token_count: int,
	scan_called: bool,
	done:        bool,
}

DEFAULT_MAX_SCAN_TOKEN_SIZE :: 1<<16;

@(private)
_INIT_BUF_SIZE :: 4096;

scanner_init :: proc(s: ^Scanner, r: io.Reader, buf_allocator := context.allocator) -> ^Scanner {
	s.r = r;
	s.split = scan_lines;
	s.max_token_size = DEFAULT_MAX_SCAN_TOKEN_SIZE;
	s.buf.allocator = buf_allocator;
	return s;
}
scanner_init_with_buffer :: proc(s: ^Scanner, r: io.Reader, buf: []byte) -> ^Scanner {
	s.r = r;
	s.split = scan_lines;
	s.max_token_size = DEFAULT_MAX_SCAN_TOKEN_SIZE;
	s.buf = mem.buffer_from_slice(buf);
	resize(&s.buf, cap(s.buf));
	return s;
}
scanner_destroy :: proc(s: ^Scanner) {
	delete(s.buf);
}


// Returns the first non-EOF error that was encounted by the scanner
scanner_error :: proc(s: ^Scanner) -> Scanner_Error {
	switch s._err {
	case .EOF, .None:
		return nil;
	}
	return s._err;
}

// Returns the most recent token created by scanner_scan.
// The underlying array may point to data that may be overwritten
// by another call to scanner_scan.
// Treat the returned value as if it is immutable.
scanner_bytes :: proc(s: ^Scanner) -> []byte {
	return s.token;
}

// Returns the most recent token created by scanner_scan.
// The underlying array may point to data that may be overwritten
// by another call to scanner_scan.
// Treat the returned value as if it is immutable.
scanner_text :: proc(s: ^Scanner) -> string {
	return string(s.token);
}

// scanner_scan advances the scanner
scanner_scan :: proc(s: ^Scanner) -> bool {
	set_err :: proc(s: ^Scanner, err: Scanner_Error) {
		err := err;
		if err == .None {
			err = nil;
		}
		switch s._err {
		case nil, .EOF:
			s._err = err;
		}
	}

	if s.done {
		return false;
	}
	s.scan_called = true;

	for {
		// Check if a token is possible with what is available
		// Allow the split procedure to recover if it fails
		if s.start < s.end || s._err != nil {
			advance, token, err, final_token := s.split(s.buf[s.start:s.end], s._err != nil);
			if final_token {
				s.token = token;
				s.done = true;
				return true;
			}
			if err != nil {
				set_err(s, err);
				return false;
			}

			// Do advance
			if advance < 0 {
				set_err(s, .Negative_Advance);
				return false;
			}
			if advance > s.end-s.start {
				set_err(s, .Advanced_Too_Far);
				return false;
			}
			s.start += advance;

			s.token = token;
			if s.token != nil {
				if s._err == nil || advance > 0 {
					s.successive_empty_token_count = 0;
				} else {
					s.successive_empty_token_count += 1;

					if s.max_consecutive_empty_reads <= 0 {
						s.max_consecutive_empty_reads = DEFAULT_MAX_CONSECUTIVE_EMPTY_READS;
					}
					if s.successive_empty_token_count > s.max_consecutive_empty_reads {
						set_err(s, .No_Progress);
						return false;
					}
				}
				return true;
			}
		}

		// If an error is hit, no token can be created
		if s._err != nil {
			s.start = 0;
			s.end = 0;
			return false;
		}

		// More data must be required to be read
		if s.start > 0 && (s.end == len(s.buf) || s.start > len(s.buf)/2) {
			copy(s.buf[:], s.buf[s.start:s.end]);
			s.end -= s.start;
			s.start = 0;
		}

		could_be_too_short := false;

		// Resize the buffer if full
		if s.end == len(s.buf) {
			if s.max_token_size <= 0 {
				s.max_token_size = DEFAULT_MAX_SCAN_TOKEN_SIZE;
			}
			if len(s.buf) >= s.max_token_size {
				set_err(s, .Too_Long);
				return false;
			}
			// overflow check
			new_size := _INIT_BUF_SIZE;
			if len(s.buf) > 0 {
				overflowed: bool;
				if new_size, overflowed = intrinsics.overflow_mul(len(s.buf), 2); overflowed {
					set_err(s, .Too_Long);
					return false;
				}
			}

			old_size := len(s.buf);
			new_size = min(new_size, s.max_token_size);
			resize(&s.buf, new_size);
			s.end -= s.start;
			s.start = 0;

			could_be_too_short = old_size >= len(s.buf);

		}

		// Read data into the buffer
		loop := 0;
		for {
			n, err := io.read(s.r, s.buf[s.end:len(s.buf)]);
			if n < 0 || len(s.buf)-s.end < n {
				set_err(s, .Bad_Read_Count);
				break;
			}
			s.end += n;
			if err != nil {
				set_err(s, err);
				break;
			}
			if n > 0 {
				s.successive_empty_token_count = 0;
				break;
			}
			loop += 1;

			if s.max_consecutive_empty_reads <= 0 {
				s.max_consecutive_empty_reads = DEFAULT_MAX_CONSECUTIVE_EMPTY_READS;
			}
			if loop > s.max_consecutive_empty_reads {
				if could_be_too_short {
					set_err(s, .Too_Short);
				} else {
					set_err(s, .No_Progress);
				}
				break;
			}
		}
	}
}

scan_bytes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
	if at_eof && len(data) == 0 {
		return;
	}
	return 1, data[0:1], nil, false;
}

scan_runes :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
	if at_eof && len(data) == 0 {
		return;
	}

	if data[0] < utf8.RUNE_SELF {
		advance = 1;
		token = data[0:1];
		return;
	}

	_, width := utf8.decode_rune(data);
	if width > 1 {
		advance = width;
		token = data[0:width];
		return;
	}

	if !at_eof && !utf8.full_rune(data) {
		return;
	}

	@thread_local ERROR_RUNE := []byte{0xef, 0xbf, 0xbd};

	advance = 1;
	token = ERROR_RUNE;
	return;
}

scan_words :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
	is_space :: proc "contextless" (r:  rune) -> bool {
		switch r {
		// lower ones
		case ' ', '\t', '\n', '\v', '\f', '\r':
			return true;
		case '\u0085', '\u00a0':
			return true;
		// higher ones
		case '\u2000' ..= '\u200a':
			return true;
		case '\u1680', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000':
			return true;
		}
		return false;
	}

	// skip spaces at the beginning
	start := 0;
	for width := 0; start < len(data); start += width {
		r: rune;
		r, width = utf8.decode_rune(data[start:]);
		if !is_space(r) {
			break;
		}
	}

	for width, i := 0, start; i < len(data); i += width {
		r: rune;
		r, width = utf8.decode_rune(data[i:]);
		if is_space(r) {
			advance = i+width;
			token = data[start:i];
			return;
		}
	}

	if at_eof && len(data) > start {
		advance = len(data);
		token = data[start:];
		return;
	}

	advance = start;
	return;
}

scan_lines :: proc(data: []byte, at_eof: bool) -> (advance: int, token: []byte, err: Scanner_Error, final_token: bool) {
	trim_carriage_return :: proc "contextless" (data: []byte) -> []byte {
		if len(data) > 0 && data[len(data)-1] == '\r' {
			return data[0:len(data)-1];
		}
		return data;
	}

	if at_eof && len(data) == 0 {
		return;
	}
	if i := bytes.index_byte(data, '\n'); i >= 0 {
		advance = i+1;
		token = trim_carriage_return(data[0:i]);
		return;
	}

	if at_eof {
		advance = len(data);
		token = trim_carriage_return(data);
	}
	return;
}
