#include <cassert>
#include "sockio_device.hpp"

#include <sys/types.h>
#include <sys/socket.h>
// provide : S_IFSOCK
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <string.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>


//-----------------------------------------------------------------------------
SockIODevice::SockIODevice( sockid s )
:sock(s)
{
    assert(s);
}

//virtual 
SockIODevice::~SockIODevice(){
    close();
}

void SockIODevice::init(sockid s){
    auto ok = isfdtype(s, S_IFSOCK);
    assert( ok );
    sock = s;
}


void SockIODevice::close(){
    shutdown(sock, SHUT_RDWR);
}

int SockIODevice::get_char(){
    unsigned c;
    auto res = recv(sock, &c, 1, MSG_WAITALL ); //MSG_DONTWAIT
    if (res <= 0)
        return res;
    return c & 0xff;
}

int SockIODevice::get_wait(unsigned to_ms){
    return get_waitfor(1, to_ms);
}

// блокирующий прием
//*  \return - длинна полученного участка
int SockIODevice::getData ( void* dst, unsigned len){
    auto res = recv(sock, dst, len, MSG_WAITALL ); //MSG_DONTWAIT
    return res;
}
//*  ожидание amount доступных символов
int SockIODevice::get_waitfor(unsigned amount, unsigned to_ms){
    if (to_ms > 0){
        int count = in_avail();
        if (count >= amount)
            return count;

    struct timeval tv = { to_ms/1000, (to_ms%1000) * 1000 };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(sock, &fds);
    int selected = select(1, &fds, nullptr, nullptr, &tv);
    if (selected <= 0)
        return selected;
    }
    //int have = recv(sock, nullptr, amount, MSG_DONTWAIT | MSG_PEEK);
    int have = in_avail();
    return have;
}

int SockIODevice::in_avail(){
    int count;
    int res = ioctl(sock, FIONREAD, &count);
    if (res >= 0)
        return count;
    return res;
}

//* блокирующая печать
int SockIODevice::putChar(int ch){
    int buf = ch;
    auto y = send(sock, &buf,  1, 0 );//MSG_DONTWAIT
    if (y == 1)
        return 1;
    return y;
}

//*  неблокирующая печать
int SockIODevice::postData ( const void* str, unsigned len){
    return send(sock, str, len, 0 );//MSG_MORE
}

//* блокирующая печать
int SockIODevice::puts( const char* str){
    return putData(str, strlen(str) );
}

//*  \return - длинна отправленного участка
int SockIODevice::putData ( const void* str, unsigned len){
    return send(sock, str, len, MSG_DONTWAIT);
}

//*  ожидание доступности печати
//*  \return - количество байт возможных для неблокирующей печати
int SockIODevice::put_wait(unsigned to){
    // TODO hardcoded 1k space availed. maybe better provide net MTU size.
    return 1<<10;
}

//*  почти тоже put_wait, ждет полного опустошения
int SockIODevice::put_flush(unsigned to){
    // TODO
    (void)to;
    return 0;
}

//*  очищает буфер, прерывая текущую отправку
int SockIODevice::put_drop(){
    // TODO
    return 0;
}



