package io

import "intrinsics"
import "core:runtime"
import "core:unicode/utf8"

Seek_From :: enum {
	Start   = 0, // seek relative to the origin of the file
	Current = 1, // seek relative to the current offset
	End     = 2, // seek relative to the end
}

Error :: enum i32 {
	// No Error
	None = 0,

	// EOF is the error returned by `read` when no more input is available
	EOF,

	// Unexpected_EOF means that EOF was encountered in the middle of reading a fixed-sized block of data
	Unexpected_EOF,

	// Short_Write means that a write accepted fewer bytes than requested but failed to return an explicit error
	Short_Write,

	// Invalid_Write means that a write returned an impossible count
	Invalid_Write,

	// Short_Buffer means that a read required a longer buffer than was provided
	Short_Buffer,

	// No_Progress is returned by some implementations of `io.Reader` when many calls
	// to `read` have failed to return any data or error.
	// This is usually a signed of a broken `io.Reader` implementation
	No_Progress,

	Invalid_Whence,
	Invalid_Offset,
	Invalid_Unread,

	Negative_Read,
	Negative_Write,
	Negative_Count,
	Buffer_Full,

	// Unknown means that an error has occurred but cannot be categorized
	Unknown,

	// Empty is returned when a procedure has not been implemented for an io.Stream
	Empty = -1,
}

Close_Proc       :: proc(using s: Stream) -> Error;
Flush_Proc       :: proc(using s: Stream) -> Error;
Seek_Proc        :: proc(using s: Stream, offset: i64, whence: Seek_From) -> (n: i64, err: Error);
Size_Proc        :: proc(using s: Stream) -> i64;
Read_Proc        :: proc(using s: Stream, p: []byte) -> (n: int, err: Error);
Read_At_Proc     :: proc(using s: Stream, p: []byte, off: i64) -> (n: int, err: Error);
Read_From_Proc   :: proc(using s: Stream, r: Reader) -> (n: i64, err: Error);
Read_Byte_Proc   :: proc(using s: Stream) -> (byte, Error);
Read_Rune_Proc   :: proc(using s: Stream) -> (ch: rune, size: int, err: Error);
Unread_Byte_Proc :: proc(using s: Stream) -> Error;
Unread_Rune_Proc :: proc(using s: Stream) -> Error;
Write_Proc       :: proc(using s: Stream, p: []byte) -> (n: int, err: Error);
Write_At_Proc    :: proc(using s: Stream, p: []byte, off: i64) -> (n: int, err: Error);
Write_To_Proc    :: proc(using s: Stream, w: Writer) -> (n: i64, err: Error);
Write_Byte_Proc  :: proc(using s: Stream, c: byte) -> Error;
Write_Rune_Proc  :: proc(using s: Stream, r: rune) -> (size: int, err: Error);
Destroy_Proc     :: proc(using s: Stream) -> Error;


Stream :: struct {
	using stream_vtable: ^Stream_VTable,
	stream_data:         rawptr,
}
Stream_VTable :: struct {
	impl_close: Close_Proc,
	impl_flush: Flush_Proc,

	impl_seek:  Seek_Proc,
	impl_size:  Size_Proc,

	impl_read:      Read_Proc,
	impl_read_at:   Read_At_Proc,
	impl_read_byte: Read_Byte_Proc,
	impl_read_rune: Read_Rune_Proc,
	impl_write_to:  Write_To_Proc,

	impl_write:      Write_Proc,
	impl_write_at:   Write_At_Proc,
	impl_write_byte: Write_Byte_Proc,
	impl_write_rune: Write_Rune_Proc,
	impl_read_from:  Read_From_Proc,

	impl_unread_byte: Unread_Byte_Proc,
	impl_unread_rune: Unread_Rune_Proc,

	impl_destroy: Destroy_Proc,
}


Reader             :: struct {using stream: Stream};
Writer             :: struct {using stream: Stream};
Closer             :: struct {using stream: Stream};
Flusher            :: struct {using stream: Stream};
Seeker             :: struct {using stream: Stream};

Read_Writer        :: struct {using stream: Stream};
Read_Closer        :: struct {using stream: Stream};
Read_Write_Closer  :: struct {using stream: Stream};
Read_Write_Seeker  :: struct {using stream: Stream};

Write_Closer       :: struct {using stream: Stream};
Write_Seeker       :: struct {using stream: Stream};
Write_Flusher      :: struct {using stream: Stream};
Write_Flush_Closer :: struct {using stream: Stream};

Reader_At          :: struct {using stream: Stream};
Writer_At          :: struct {using stream: Stream};
Reader_From        :: struct {using stream: Stream};
Writer_To          :: struct {using stream: Stream};

Byte_Reader        :: struct {using stream: Stream};
Byte_Scanner       :: struct {using stream: Stream};
Byte_Writer        :: struct {using stream: Stream};

Rune_Reader        :: struct {using stream: Stream};
Rune_Scanner       :: struct {using stream: Stream};


destroy :: proc(s: Stream) -> Error {
	close_err := close({s});
	if s.stream_vtable != nil && s.impl_destroy != nil {
		return s->impl_destroy();
	}
	if close_err != .None {
		return close_err;
	}
	return .Empty;
}

read :: proc(s: Reader, p: []byte) -> (n: int, err: Error) {
	if s.stream_vtable != nil && s.impl_read != nil {
		return s->impl_read(p);
	}
	return 0, .Empty;
}

write :: proc(s: Writer, p: []byte) -> (n: int, err: Error) {
	if s.stream_vtable != nil && s.impl_write != nil {
		return s->impl_write(p);
	}
	return 0, .Empty;
}

seek :: proc(s: Seeker, offset: i64, whence: Seek_From) -> (n: i64, err: Error) {
	if s.stream_vtable != nil && s.impl_seek != nil {
		return s->impl_seek(offset, whence);
	}
	return 0, .Empty;
}

close :: proc(s: Closer) -> Error {
	if s.stream_vtable != nil && s.impl_close != nil {
		return s->impl_close();
	}
	// Instead of .Empty, .None is fine in this case
	return .None;
}

flush :: proc(s: Flusher) -> Error {
	if s.stream_vtable != nil && s.impl_flush != nil {
		return s->impl_flush();
	}
	// Instead of .Empty, .None is fine in this case
	return .None;
}

size :: proc(s: Stream) -> i64 {
	if s.stream_vtable == nil {
		return 0;
	}
	if s.impl_size != nil {
		return s->impl_size();
	}
	if s.impl_seek == nil {
		return 0;
	}

	curr, end: i64;
	err: Error;
	if curr, err = s->impl_seek(0, .Current); err != nil {
		return 0;
	}

	if end, err = s->impl_seek(0, .End); err != nil {
		return 0;
	}

	if _, err = s->impl_seek(curr, .Start); err != nil {
		return 0;
	}

	return end;
}




read_at :: proc(r: Reader_At, p: []byte, offset: i64) -> (n: int, err: Error) {
	if r.stream_vtable == nil {
		return 0, .Empty;
	}
	if r.impl_read_at != nil {
		return r->impl_read_at(p, offset);
	}
	if r.impl_seek == nil || r.impl_read == nil {
		return 0, .Empty;
	}

	curr_offset: i64;
	curr_offset, err = r->impl_seek(offset, .Current);
	if err != nil {
		return 0, err;
	}

	n, err = r->impl_read(p);
	_, err1 := r->impl_seek(curr_offset, .Start);
	if err1 != nil && err == nil {
		err = err1;
	}
	return;

}

write_at :: proc(w: Writer_At, p: []byte, offset: i64) -> (n: int, err: Error) {
	if w.stream_vtable == nil {
		return 0, .Empty;
	}
	if w.impl_write_at != nil {
		return w->impl_write_at(p, offset);
	}
	if w.impl_seek == nil || w.impl_write == nil {
		return 0, .Empty;
	}

	curr_offset: i64;
	curr_offset, err = w->impl_seek(offset, .Current);
	if err != nil {
		return 0, err;
	}

	n, err = w->impl_write(p);
	_, err1 := w->impl_seek(curr_offset, .Start);
	if err1 != nil && err == nil {
		err = err1;
	}
	return;
}

write_to :: proc(r: Writer_To, w: Writer) -> (n: i64, err: Error) {
	if r.stream_vtable == nil || w.stream_vtable == nil {
		return 0, .Empty;
	}
	if r.impl_write_to != nil {
		return r->impl_write_to(w);
	}
	return 0, .Empty;
}
read_from :: proc(w: Reader_From, r: Reader) -> (n: i64, err: Error) {
	if r.stream_vtable == nil || w.stream_vtable == nil {
		return 0, .Empty;
	}
	if r.impl_read_from != nil {
		return w->impl_read_from(r);
	}
	return 0, .Empty;
}


read_byte :: proc(r: Byte_Reader) -> (byte, Error) {
	if r.stream_vtable == nil {
		return 0, .Empty;
	}
	if r.impl_read_byte != nil {
		return r->impl_read_byte();
	}
	if r.impl_read == nil {
		return 0, .Empty;
	}

	b: [1]byte;
	_, err := r->impl_read(b[:]);
	return b[0], err;
}

write_byte :: proc{
	write_byte_to_byte_writer,
	write_byte_to_writer,
};

write_byte_to_byte_writer :: proc(w: Byte_Writer, c: byte) -> Error {
	return _write_byte(w, c);
}

write_byte_to_writer :: proc(w: Writer, c: byte) -> Error {
	return _write_byte(auto_cast w, c);
}

@(private)
_write_byte :: proc(w: Byte_Writer, c: byte) -> Error {
	if w.stream_vtable == nil {
		return .Empty;
	}
	if w.impl_write_byte != nil {
		return w->impl_write_byte(c);
	}
	if w.impl_write == nil {
		return .Empty;
	}

	b := [1]byte{c};
	_, err := w->impl_write(b[:]);
	return err;
}

read_rune :: proc(br: Rune_Reader) -> (ch: rune, size: int, err: Error) {
	if br.stream_vtable == nil {
		return 0, 0, .Empty;
	}
	if br.impl_read_rune != nil {
		return br->impl_read_rune();
	}
	if br.impl_read == nil {
		return 0, 0, .Empty;
	}

	b: [utf8.UTF_MAX]byte;
	_, err = br->impl_read(b[:1]);

	s0 := b[0];
	ch = rune(s0);
	size = 1;
	if err != nil {
		return;
	}
	if ch < utf8.RUNE_SELF {
		return;
	}
	x := utf8.accept_sizes[s0];
	if x >= 0xf0 {
		mask := rune(x) << 31 >> 31;
		ch = ch &~ mask | utf8.RUNE_ERROR&mask;
		return;
	}
	sz := int(x&7);
	n: int;
	n, err = br->impl_read(b[1:sz]);
	if err != nil || n+1 < sz {
		ch = utf8.RUNE_ERROR;
		return;
	}

	ch, size = utf8.decode_rune(b[:sz]);
	return;
}

unread_byte :: proc(s: Byte_Scanner) -> Error {
	if s.stream_vtable != nil && s.impl_unread_byte != nil {
		return s->impl_unread_byte();
	}
	return .Empty;
}
unread_rune :: proc(s: Rune_Scanner) -> Error {
	if s.stream_vtable != nil && s.impl_unread_rune != nil {
		return s->impl_unread_rune();
	}
	return .Empty;
}


write_string :: proc(s: Writer, str: string) -> (n: int, err: Error) {
	return write(s, transmute([]byte)str);
}

write_rune :: proc(s: Writer, r: rune) -> (size: int, err: Error) {
	if s.stream_vtable != nil && s.impl_write_rune != nil {
		return s->impl_write_rune(r);
	}

	if r < utf8.RUNE_SELF {
		err = write_byte(s, byte(r));
		if err == nil {
			size = 1;
		}
		return;
	}
	buf, w := utf8.encode_rune(r);
	return write(s, buf[:w]);
}



read_full :: proc(r: Reader, buf: []byte) -> (n: int, err: Error) {
	return read_at_least(r, buf, len(buf));
}


read_at_least :: proc(r: Reader, buf: []byte, min: int) -> (n: int, err: Error) {
	if len(buf) < min {
		return 0, .Short_Buffer;
	}
	for n < min && err == nil {
		nn: int;
		nn, err = read(r, buf[n:]);
		n += n;
	}

	if n >= min {
		err = nil;
	} else if n > 0 && err == .EOF {
		err = .Unexpected_EOF;
	}
	return;
}

// copy copies from src to dst till either EOF is reached on src or an error occurs
// It returns the number of bytes copied and the first error that occurred whilst copying, if any.
copy :: proc(dst: Writer, src: Reader) -> (written: i64, err: Error) {
	return _copy_buffer(dst, src, nil);
}

// copy_buffer is the same as copy except that it stages through the provided buffer (if one is required)
// rather than allocating a temporary one on the stack through `intrinsics.alloca`
// If buf is `nil`, it is allocate through `intrinsics.alloca`; otherwise if it has zero length, it will panic
copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, err: Error) {
	if buf != nil && len(buf) == 0 {
		panic("empty buffer in io.copy_buffer");
	}
	return _copy_buffer(dst, src, buf);
}



// copy_n copies n bytes (or till an error) from src to dst.
// It returns the number of bytes copied and the first error that occurred whilst copying, if any.
// On return, written == n IFF err == nil
copy_n :: proc(dst: Writer, src: Reader, n: i64) -> (written: i64, err: Error) {
	nsrc := limited_reader_init(&Limited_Reader{}, src, n);
	written, err = copy(dst, nsrc);
	if written == n {
		return n, nil;
	}
	if written < n && err == nil {
		// src stopped early and must have been an EOF
		err = .EOF;
	}
	return;
}


@(private)
_copy_buffer :: proc(dst: Writer, src: Reader, buf: []byte) -> (written: i64, err: Error) {
	if dst.stream_vtable == nil || src.stream_vtable == nil {
		return 0, .Empty;
	}
	if src.impl_write_to != nil {
		return src->impl_write_to(dst);
	}
	if src.impl_read_from != nil {
		return dst->impl_read_from(src);
	}
	buf := buf;
	if buf == nil {
		DEFAULT_SIZE :: 4 * 1024;
		size := DEFAULT_SIZE;
		if src.stream_vtable == _limited_reader_vtable {
			l := (^Limited_Reader)(src.stream_data);
			if i64(size) > l.n {
				if l.n < 1 {
					size = 1;
				} else {
					size = int(l.n);
				}
			}
		}
		// NOTE(bill): alloca is fine here
		buf = transmute([]byte)runtime.Raw_Slice{intrinsics.alloca(size, 2*align_of(rawptr)), size};
	}
	for {
		nr, er := read(src, buf);
		if nr > 0 {
			nw, ew := write(dst, buf[0:nr]);
			if nw > 0 {
				written += i64(nw);
			}
			if ew != nil {
				err = ew;
				break;
			}
			if nr != nw {
				err = .Short_Write;
				break;
			}
		}
		if er != nil {
			if er != .EOF {
				err = er;
			}
			break;
		}
	}
	return;
}
