#include <sys/types.h>

#include "stream.h"


#ifdef _WIN32
    #define NON_BLOCKING(error) ((error) == WSAEWOULDBLOCK) 
    #include <basetsd.h>
typedef SSIZE_T ssize_t;
#else
    #include <errno.h>
    #define NON_BLOCKING(error) ((error) == EAGAIN || (error) == EWOULDBLOCK)
#endif


ssize_t stream_receive(socket_t sock, Buffer *buf, stream_data_handler_fn handler, void *user_data) {

    #ifndef _WIN32
        errno = 0;
    #endif

    ssize_t n = recv(sock, buf->array + buf->end, buffer_available(buf), 0);

    if (n < 0) {

        #ifdef _WIN32
            int error = WSAGetLastError();
        #else
            int error = errno;
        #endif

        if (NON_BLOCKING(error)) {
            // Socket is non-blocking and no data is ready yet. Not an error.
            return 0;
        }

        // actual error
        return -1;

    }

    if (n == 0) {
        // end of stream
        return -1;
    }

    buf->end += n;

    // If the buffer was completely filled, double its capacity.
    // This is preventative in order to avoid too many partial reads.
    buffer_reserve(buf, 1);

    char *start = buf->array + buf->start;
    char *end = buf->array + buf->end;
    char *p = start;

    while (p < end) {

        ssize_t m = handler(p, end - p, user_data);

        if (m < 0) {
            return -1;
        }

        if (m == 0) {
            break;
        }

        p += m;

    }

    buffer_skip(buf, p - start);
    buffer_pack(buf);

    return n;  // bytes received

}

ssize_t stream_send(socket_t sock, Buffer *buf) {

    size_t size = buffer_size(buf);

    #ifndef _WIN32
        errno = 0;
    #endif

    ssize_t n = send(sock, buf->array + buf->start, size, 0);

    if (n < 0) {

        #ifdef _WIN32
            int error = WSAGetLastError();
        #else
            int error = errno;
        #endif

        if (NON_BLOCKING(error)) {
            // Socket is non-blocking and the write couldn't complete right now. Not an error.
            return 0;
        }

        // actual error
        return -1;

    }

    buffer_skip(buf, n);

    return n;

}
