package bufio

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

// Writer is a buffered wrapper for an io.Writer
Writer :: struct {
	buf:            []byte,
	buf_allocator:  mem.Allocator,

	wr: io.Writer,
	n: int,

	err: io.Error,

	max_consecutive_empty_writes: int,

}

writer_init :: proc(b: ^Writer, wr: io.Writer, size: int = DEFAULT_BUF_SIZE, allocator := context.allocator) {
	size := size;
	size = max(size, MIN_READ_BUFFER_SIZE);
	writer_reset(b, wr);
	b.buf_allocator = allocator;
	b.buf = make([]byte, size, allocator);
}

writer_init_with_buf :: proc(b: ^Writer, wr: io.Writer, buf: []byte) {
	writer_reset(b, wr);
	b.buf_allocator = {};
	b.buf = buf;
}

// writer_destroy destroys the underlying buffer with its associated allocator IFF that allocator has been set
writer_destroy :: proc(b: ^Writer) {
	delete(b.buf, b.buf_allocator);
	b^ = {};
}

// writer_size returns the size of underlying buffer in bytes
writer_size :: proc(b: ^Writer) -> int {
	return len(b.buf);
}

writer_reset :: proc(b: ^Writer, w: io.Writer) {
	b.wr = w;
	b.n = 0;
	b.err = nil;
}


// writer_flush writes any buffered data into the underlying io.Writer
writer_flush :: proc(b: ^Writer) -> io.Error {
	if b.err != nil {
		return b.err;
	}
	if b.n == 0 {
		return nil;
	}

	n, err := io.write(b.wr, b.buf[0:b.n]);
	if n < b.n && err == nil {
		err = .Short_Write;
	}
	if err != nil {
		if n > 0 && n < b.n {
			copy(b.buf[:b.n-n], b.buf[n : b.n]);
		}
		b.n -= n;
		b.err = err;
		return err;
	}
	b.n = 0;
	return nil;
}

// writer_available returns how many bytes are unused in the buffer
writer_available :: proc(b: ^Writer) -> int {
	return len(b.buf) - b.n;
}

// writer_buffered returns the number of bytes that have been writted into the current buffer
writer_buffered :: proc(b: ^Writer) -> int {
	return b.n;
}

// writer_write writes the contents of p into the buffer
// It returns the number of bytes written
// If n < len(p), it will return an error explaining why the write is short
writer_write :: proc(b: ^Writer, p: []byte) -> (n: int, err: io.Error) {
	p := p;
	for len(p) > writer_available(b) && b.err == nil {
		m: int;
		if writer_buffered(b) == 0 {
			m, b.err = io.write(b.wr, p);
		} else {
			m = copy(b.buf[b.n:], p);
			b.n += m;
			writer_flush(b);
		}
		n += m;
		p = p[m:];
	}
	if b.err != nil {
		return n, b.err;
	}
	m := copy(b.buf[b.n:], p);
	b.n += m;
	m += n;
	return m, nil;
}

// writer_write_byte writes a single byte
writer_write_byte :: proc(b: ^Writer, c: byte) -> io.Error {
	if b.err != nil {
		return b.err;
	}
	if writer_available(b) <= 0 && writer_flush(b) != nil {
		return b.err;
	}
	b.buf[b.n] = c;
	b.n += 1;
	return nil;
}

// writer_write_rune writes a single unicode code point, and returns the number of bytes written with any error
writer_write_rune :: proc(b: ^Writer, r: rune) -> (size: int, err: io.Error) {
	if r < utf8.RUNE_SELF {
		err = writer_write_byte(b, byte(r));
		size = 0 if err != nil else 1;
		return;
	}
	if b.err != nil {
		return 0, b.err;
	}

	buf: [4]u8;

	n := writer_available(b);
	if n < utf8.UTF_MAX {
		writer_flush(b);
		if b.err != nil {
			return 0, b.err;
		}
		n = writer_available(b);
		if n < utf8.UTF_MAX {
			// this only happens if the buffer is very small
			w: int;
			buf, w = utf8.encode_rune(r);
			return writer_write(b, buf[:w]);
		}
	}

	buf, size = utf8.encode_rune(r);
	copy(b.buf[b.n:], buf[:size]);
	b.n += size;
	return;
}

// writer_write writes a string into the buffer
// It returns the number of bytes written
// If n < len(p), it will return an error explaining why the write is short
writer_write_string :: proc(b: ^Writer, s: string) -> (int, io.Error) {
	return writer_write(b, transmute([]byte)s);
}

// writer_read_from is to support io.Reader_From types
// If the underlying writer supports the io,read_from, and b has no buffered data yet,
// this procedure calls the underlying read_from implementation without buffering
writer_read_from :: proc(b: ^Writer, r: io.Reader) -> (n: i64, err: io.Error) {
	if b.err != nil {
		return 0, b.err;
	}
	if writer_buffered(b) == 0 {
		if w, ok := io.to_reader_from(b.wr); !ok {
			n, err = io.read_from(w, r);
			b.err = err;
			return;
		}
	}

	for {
		if writer_available(b) == 0 {
			if ferr := writer_flush(b); ferr != nil {
				return n, ferr;
			}
		}
		if b.max_consecutive_empty_writes <= 0 {
			b.max_consecutive_empty_writes = DEFAULT_MAX_CONSECUTIVE_EMPTY_READS;
		}

		m: int;
		nr := 0;
		for nr < b.max_consecutive_empty_writes {
			m, err = io.read(r, b.buf[b.n:]);
			if m != 0 || err != nil {
				break;
			}
			nr += 1;
		}
		if nr == b.max_consecutive_empty_writes {
			return n, .No_Progress;
		}
		b.n += m;
		n += i64(m);
		if err != nil {
			break;
		}
	}

	if err == .EOF {
		if writer_available(b) == 0 {
			err = writer_flush(b);
		} else {
			err = nil;
		}
	}
	return;
}



// writer_to_stream converts a Writer into an io.Stream
writer_to_stream :: proc(b: ^Writer) -> (s: io.Stream) {
	s.stream_data = b;
	s.stream_vtable = _writer_vtable;
	return;
}



@(private)
_writer_vtable := &io.Stream_VTable{
	impl_destroy = proc(s: io.Stream) -> io.Error {
		b := (^Writer)(s.stream_data);
		writer_destroy(b);
		return nil;
	},
	impl_flush = proc(s: io.Stream)  -> io.Error {
		b := (^Writer)(s.stream_data);
		return writer_flush(b);
	},
	impl_write = proc(s: io.Stream, p: []byte) -> (n: int, err: io.Error) {
		b := (^Writer)(s.stream_data);
		return writer_write(b, p);
	},
	impl_write_byte = proc(s: io.Stream, c: byte) -> io.Error {
		b := (^Writer)(s.stream_data);
		return writer_write_byte(b, c);
	},
	impl_write_rune = proc(s: io.Stream, r: rune) -> (int, io.Error) {
		b := (^Writer)(s.stream_data);
		return writer_write_rune(b, r);
	},
	impl_read_from = proc(s: io.Stream, r: io.Reader) -> (n: i64, err: io.Error) {
		b := (^Writer)(s.stream_data);
		return writer_read_from(b, r);
	},
};
