
import io.errors.*;
import vectors.generic.*;

//
// read, write, flush - byte stream operations
//

// read(stream, buf:Pointer[Byte], n:SizeT) SizeT
define read;

// write(stream, buf:Pointer[Byte], n:SizeT) Void
define write;

// flush(stream) Void
define flush;


[T]
ByteLike?(static T) = ByteSizedInteger?(T) or (T == Char);

[T]
ByteVector?(static T) = Vector?(T) and ByteLike?(SequenceElementType(T));

[T]
ByteBuffer?(static T) = ContiguousSequence?(T) and ByteLike?(SequenceElementType(T));


//
// readByte, readChar
//

readByte(stream, b:Byte) : Bool {
    var result = read(stream, &b, SizeT(1));
    return result == 1;
}

readChar(stream, c:Char) : Bool {
    var ptr = Pointer[Byte](&c);
    var result = read(stream, ptr, SizeT(1));
    return result == 1;
}


//
// read - read into byte vector
//

[V | ByteVector?(V)]
overload read(stream, n:SizeT, buf:V) {
    var current = size(buf);
    resize(buf, current + n);
    var ptr = Pointer[Byte](begin(buf) + current);
    var numRead = read(stream, ptr, n);
    resize(buf, current + numRead);
    return numRead;
}

overload read(stream, n:SizeT) {
    var buf = String();
    read(stream, n, buf);
    return move(buf);
}


//
// readN - read exactly n bytes
//

record PrematureEOF(expected:SizeT, received:SizeT);
instance IOError (PrematureEOF);

define readN;

[V | ByteVector?(V)]
overload readN(stream, n:SizeT, buf:V) {
    var initial = size(buf);
    var current = initial;
    var expected = initial + n;
    while (current < expected) {
        var numRead = read(stream, expected - current, buf);
        if (numRead == 0)
            throw PrematureEOF(expected-initial, current-initial);
        current += numRead;
    }
}

overload readN(stream, buffer:Pointer[Byte], n:SizeT) {
    var bv = BufferVector(buffer, buffer + n);
    readN(stream, n, bv);
}

overload readN(stream, n:SizeT) {
    var buf = String();
    readN(stream, n, buf);
    return move(buf);
}


//
// readAll
//

define readAll;

[V | ByteVector?(V)]
overload readAll(stream, chunkSize:SizeT, buf:V) {
    while (true) {
        var numRead = read(stream, chunkSize, buf);
        if (numRead == 0)
            break;
    }
}

[V | ByteVector?(V)]
overload readAll(stream, buf:V) {
    readAll(stream, SizeT(4096), buf);
}

overload readAll(stream) : String {
    var buf = String();
    readAll(stream, buf);
    return move(buf);
}


//
// readUpto, readLine
//

define readUpto;

[T, V | ByteLike?(T) and ByteVector?(V)]
overload readUpto(stream, upto:T, buf:V) {
    alias ElementType = SequenceElementType(V);
    var b = Byte();
    var uptoByte = Byte(upto);
    while (readByte(stream, b)) {
        push(buf, ElementType(b));
        if (b == uptoByte)
            break;
    }
}

[T | ByteLike?(T)]
overload readUpto(stream, upto:T) : String {
    var v = String();
    readUpto(stream, upto, v);
    return move(v);
}


define readLine;

[V | ByteVector?(V)]
overload readLine(stream, buf:V) {
    readUpto(stream, '\n', buf);
}

overload readLine(stream) = readUpto(stream, '\n');


//
// write - write one byte
//

[I | Integer?(I)]
overload write(stream, x:I) {
    var b = Byte(x);
    write(stream, &b, SizeT(1));
}

overload write(stream, x:Char) {
    write(stream, Byte(x));
}


//
// write - write byte buffer
//

[V | ByteBuffer?(V)]
overload write(stream, x:V) {
    write(stream, Pointer[Byte](begin(x)), size(x));
}


//
// write, flush - write to ByteVector
//

[V | ByteVector?(V)]
overload write(stream:V, buf:Pointer[Byte], n:SizeT) {
    var currentSize = size(stream);
    resize(stream, currentSize + n);
    var dest = Pointer[Byte](begin(stream)) + currentSize;
    copyNonoverlappingMemory(dest, buf, buf+n);
}

[V | ByteVector?(V)]
overload flush(stream:V) {
}
