%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2014-2016, 2018 The Mercury Team.
% This file is distributed under the terms specified in COPYING.LIB.
%---------------------------------------------------------------------------%
%
% Module:               net.streams.
% Main Author:          Paul Bone
% Stability:            low
%
% Provide a streams interface for sockets.
%
%---------------------------------------------------------------------------%

:- module net.streams.
:- interface.

:- import_module int.
:- import_module io.
:- import_module stream.
:- import_module string.

:- import_module net.sockets.

%---------------------------------------------------------------------------%

:- type socket_stream.

:- func stream(socket) = socket_stream.

:- func socket(socket_stream) = socket.

:- type byte
    --->    byte(int).

:- type error.

:- instance error(streams.error).

:- instance stream(socket_stream, io).

%---------------------------------------------------------------------------%

:- instance input(socket_stream, io).

    % XXX: This does not buffer reads, it is slow.
    %
:- instance reader(socket_stream, streams.byte, io, streams.error).

:- instance reader(socket_stream, line, io, streams.error).

%---------------------------------------------------------------------------%

:- instance output(socket_stream, io).

    % XXX: This does not buffer writes, it is slow.
    %
:- instance writer(socket_stream, streams.byte, io).

:- instance writer(socket_stream, string, io).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.

:- import_module char.
:- import_module exception.
:- import_module bitmap.
:- import_module list.
:- import_module maybe.
:- import_module require.

:- type socket_stream
    --->    socket_stream(socket).

:- type error
    --->    error(string).

stream(Socket) = socket_stream(Socket).

socket(socket_stream(Socket)) = Socket.

%---------------------------------------------------------------------------%

:- instance error(streams.error) where [
    error_message(error(Str)) = Str
].

:- instance stream(socket_stream, io) where [
    pred(name/4) is stream_name
].

:- pred stream_name(socket_stream::in, name::out, io::di, io::uo) is det.

stream_name(socket_stream(_Socket), Name, !IO) :-
    Name = "a socket".

%---------------------------------------------------------------------------%

:- instance input(socket_stream, io) where [].

:- instance reader(socket_stream, streams.byte, io, streams.error) where [
    pred(get/4) is get_byte
].

:- pred get_byte(socket_stream::in, result(streams.byte, streams.error)::out,
    io::di, io::uo) is det.

get_byte(socket_stream(Socket), Result, !IO) :-
    read(Socket, 1, ReadResult, !IO),
    (
        ReadResult = ok(Bitmap),
        ( if num_bytes(Bitmap) = 1 then
            Byte = Bitmap ^ byte(0),
            Result = ok(byte(Byte))
        else if num_bytes(Bitmap) = 0 then
            Result = eof
        else
            unexpected($file, $pred,
                "Read returned unexpected number of bytes")
        )
    ;
        ReadResult = eof,
        Result = eof
    ;
        ReadResult = error(String),
        Result = error(error(String))
    ).

:- instance reader(socket_stream, line, io, streams.error) where [
        pred(get/4) is get_line
    ].

:- pred get_line(socket_stream::in, result(line, streams.error)::out,
    io::di, io::uo) is det.

get_line(Stream, Result, !IO) :-
    get_chars_until_nl(Stream, [], Result0, !IO),
    (
        Result0 = ok(RevChars),
        Result = ok(line(from_rev_char_list(RevChars)))
    ;
        Result0 = eof,
        Result = eof
    ;
        Result0 = error(Error),
        Result = error(Error)
    ).

:- pred get_chars_until_nl(Stream::in, list(char)::in,
    result(list(char), Error)::out, State::di, State::uo) is det
    <= reader(Stream, streams.byte, State, Error).

get_chars_until_nl(Stream, Chars0, Result, !IO) :-
    get(Stream, ResByte, !IO),
    (
        ResByte = ok(byte(Byte)),
        ( char.from_int(Byte, Char) ->
            ( if
                ( Char = '\n'
                ; Char = '\r'
                )
            then
                Result = ok(Chars0)
            else
                Chars1 = [Char | Chars0],
                get_chars_until_nl(Stream, Chars1, Result, !IO)
            )
        ;
            unexpected($file, $pred, "Encoding error")
        )
    ;
        ResByte = eof,
        Result = eof
    ;
        ResByte = error(Error),
        Result = error(Error)
    ).


%---------------------------------------------------------------------------%

:- instance output(socket_stream, io) where [
    pred(flush/3) is flush_noop
].

:- pred flush_noop(socket_stream::in, io::di, io::uo) is det.

flush_noop(_, !IO).

    % XXX: This does not buffer writes, it is slow.
    %
:- instance writer(socket_stream, streams.byte, io) where [
    pred(put/4) is put_byte
].

:- pred put_byte(socket_stream::in, streams.byte::in, io::di, io::uo)
    is det.

put_byte(socket_stream(Socket), byte(Byte), !IO) :-
    Bitmap = init(bits_per_byte) ^ byte(0) := Byte,
    write(Socket, Bitmap, Result, !IO),
    (
        Result = ok
    ;
        Result = error(Error),
        throw(streams.error(Error))
    ).

:- instance writer(socket_stream, string, io) where [
    pred(put/4) is put_string
].

:- pred put_string(Stream::in, string::in, State::di, State::uo) is det
    <= writer(Stream, streams.byte, State).

put_string(Stream, String, !State) :-
    foldl(put_char(Stream), String, !State).

:- pred put_char(Stream::in, char::in, State::di, State::uo) is det
    <= writer(Stream, streams.byte, State).

put_char(Stream, Char, !State) :-
    put(Stream, byte(to_int(Char)), !State).

%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
