#include "../src/nasal.h"
#include "../src/nasal_type.h"
#include "../src/nasal_gc.h"
#include <iostream>

#ifndef _MSC_VER
#include <unistd.h>
#endif

#ifdef _MSC_VER
#pragma warning (disable:4996)
#endif

#ifdef _WIN32
#include <conio.h>
#else
#include <fcntl.h>
#include <termios.h>
#endif

namespace nasal {

class noecho_input {
private:
#ifndef _WIN32
    struct termios init_termios;
    struct termios new_termios;
    int peek_char = -1;
#endif
public:
    noecho_input() {
#ifndef _WIN32
        tcflush(0, TCIOFLUSH);
        tcgetattr(0, &init_termios);
        new_termios = init_termios;
        new_termios.c_lflag &= ~(ICANON|ECHO|ECHONL|ECHOE);
        // vmin = 0 is nonblock input,
        // but in wsl1 there is a bug that will block input
        // so we use fcntl to write the nonblock input
        new_termios.c_cc[VMIN] = 1;
        new_termios.c_cc[VTIME] = 0;
        tcsetattr(0, TCSANOW, &new_termios);
#endif
    }

    ~noecho_input() {
#ifndef _WIN32
        tcflush(0, TCIOFLUSH);
        tcsetattr(0, TCSANOW, &init_termios);
#endif
    }

    int noecho_kbhit() {
#ifndef _WIN32
        unsigned char ch = 0;
        int nread = 0;
        if (peek_char!=-1) {
            return 1;
        }
        int flag = fcntl(0, F_GETFL);
        fcntl(0, F_SETFL, flag|O_NONBLOCK);
        nread = read(0, &ch, 1);
        fcntl(0, F_SETFL, flag);
        if (nread==1) {
            peek_char = ch;
            return 1;
        }
        return 0;
#else
        return kbhit();
#endif
    }

    int noecho_getch() {
#ifndef _WIN32
        int ch = 0;
        if (peek_char!=-1) {
            ch = peek_char;
            peek_char = -1;
            return ch;
        }
        ssize_t tmp = read(0, &ch, 1);
        return ch;
#else
        return getch();
#endif
    }
};

noecho_input this_window;

var nas_getch(var* args, usize size, gc* ngc) {
    return var::num(static_cast<double>(this_window.noecho_getch()));
}

var nas_kbhit(var* args, usize size, gc* ngc) {
    return var::num(static_cast<double>(this_window.noecho_kbhit()));
}

var nas_noblock(var* args, usize size, gc* ngc) {
    if (this_window.noecho_kbhit()) {
        return var::num(static_cast<double>(this_window.noecho_getch()));
    }
    return nil;
}

module_func_info func_tbl[] = {
    {"nas_getch", nas_getch},
    {"nas_kbhit", nas_kbhit},
    {"nas_noblock", nas_noblock},
    {nullptr, nullptr}
};

NASAL_EXTERN module_func_info* get() {
    return func_tbl;
}

}