#include "stdio_device.hpp"
#include <cassert>
#include <fcntl.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/ioctl.h>



// https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
#include <termios.h>

void disableRawMode(int fdno, termios& saveios) {
  tcsetattr(fdno, TCSAFLUSH, &saveios);
}

void enableRawMode(int fdno, termios* saveios) {
    tcgetattr(fdno, saveios);

    struct termios raw = *saveios;
    // ICANON - buffering line by line
    raw.c_lflag &= ~(ECHO | ICANON);
    tcsetattr(fdno, TCSAFLUSH, &raw);
}




#include <cstdlib>
struct termios orig_termios;

extern "C"
void disableRawMode(){
    disableRawMode(STDIN_FILENO, orig_termios);
}

void enableRawMode(int fdno) {
    enableRawMode(fdno, &orig_termios);
    atexit( &disableRawMode );
}


//-----------------------------------------------------------------------------
STDIODevice::STDIODevice( FILE* in, std::FILE* out) 
:cin(in), cout(out) {
    assert(cin);
    assert(cout);
    //flockfile(cin);
    // provide nonblocking on input
    fdin = fileno(cin);
    int flags = fcntl( fdin, F_GETFL, 0);
    enableRawMode(fdin, &orig_termios);
    fcntl(fdin, F_SETFL, flags | O_NONBLOCK);
    // выключаю буферирование ввода, ибо оно глючит - вводновой строки 
    setvbuf(cin, nullptr, _IONBF, 1024);
    
}

//virtual 
STDIODevice::~STDIODevice(){
    disableRawMode(fdin, orig_termios);
    //funlockfile(cin);
}

int STDIODevice::get_char(){
    return fgetc(cin); //fgetc_unlocked(cin);
}

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

// блокирующий прием
//*  \return - длинна полученного участка
int STDIODevice::getData ( void* dst, unsigned len){
    return fread(dst, len, 1, cin);
}

//*  ожидание amount доступных символов
int STDIODevice::get_waitfor(unsigned amount, unsigned to_ms){
    if (to_ms > 0) {
        int have = in_avail();
        if (have >= amount)
            return have;

    struct timeval tv = { to_ms/1000, (to_ms%1000) * 1000 };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(fdin, &fds);
    int selected = select(1, &fds, nullptr, nullptr, &tv);
    if (selected <= 0)
        return selected;
    }
    return in_avail();
}

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

//* блокирующая печать
int STDIODevice::putChar(int ch){
    int y = fputc(ch,  cout);
    if (y == ch)
        return 1;
    return y;
}

//*  неблокирующая печать
int STDIODevice::postData ( const void* str, unsigned len){
    return fwrite(str, 1, len, cout);
}

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

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

//*  ожидание доступности печати
//*  \return - количество байт возможных для неблокирующей печати
int STDIODevice::put_wait(unsigned to){
    (void)to;
    return (~0u>>1);
}

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

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



