// A very dumb, not-quite-Telnet program for testing purposes
// If running from a VM, on the host you can do something like:

// socat TCP-LISTEN:2323,bind=10.20.0.254,reuseaddr,fork EXEC:"/bin/bash",pty,stderr,setsid,sigint,sane

// and connect to the host from Erythros with "telnet 10.20.0.254 2323"

#define TELNET_WILL 251
#define TELNET_WONT 252
#define TELNET_DO 253
#define TELNET_DONT 254
#define TELNET_IAC 255

#define TELNET_CONNECTION_TIMED_OUT cnts.jiffies > ts_begin + ts_timeout

class TelnetSession {
    @shell* sh;
    TlsSocket* s;
};

I64 @telnet_ignore_commands(U8* buf)
{
    I64 i = 0;
    while (buf[i] == TELNET_IAC) {
        switch (buf[i + 1]) {
        case TELNET_WILL:
        case TELNET_WONT:
            buf[i + 1] = TELNET_DONT;
            break;
        case TELNET_DO:
        case TELNET_DONT:
            buf[i + 1] = TELNET_WONT;
            break;
        }
        i += 3;
    }
    return i;
}

U0 @telnet_session(TelnetSession* session)
{
    U8 chars[1040];
    U8* buf = &chars;
    I64 cnt = 1;
    I64 i;

    while (cnt && session->s->state != TCP_SOCKET_STATE_CLOSED) {
        cnt = session->s->receive(buf, 1024);
        if (cnt) {
            buf[cnt] = NULL;
            i = 0;
            while (i < cnt) {
                switch (buf[i]) {
                case TELNET_IAC:
                    i += @telnet_ignore_commands(buf);
                    session->s->send(buf, i);
                    buf += i;
                    break;
                default:
                    Stdio.WriteLine(session->sh, buf);
                    i = cnt;
                    break;
                }
            }
        }
        Sleep(1);
    }
}

U0 @telnet_handle_input(@shell* sh, TlsSocket* s)
{
    U8 buf[128];
    I64 len = 0;
    while (FifoU8Cnt(sh->input))
        FifoU8Rem(sh->input, buf + len++);
    if (len)
        s->send(buf, len);
}

I64 @shell_cmd_telnet(@shell* sh, I64 argc, U8** argv)
{
    if (argc < 2) {
        Stdio.WriteLine(sh, "Usage: telnet host [port]\n");
        return 1;
    }

    U8 buf[128];
    U8* host = argv[1];
    I64 port = 23;
    if (argc > 2) {
        port = Str2I64(argv[2]);
    }

    I64 ts_begin = 0;
    I64 ts_timeout = 10000;

    if (!host || !StrLen(host)) {
        Stdio.WriteLine(sh, "telnet: Invalid host specified\n");
        return 1;
    }

    if (@dns_query(host) == U32_MAX) {
        Stdio.WriteLine(sh, "telnet: Host not found: %s\n", host);
        return 1;
    }

    StrPrint(buf, "Trying %s port %d...\n", host, port);
    Stdio.WriteLine(sh, buf);

    TelnetSession session;
    session.sh = sh;
    session.s = @tcp_socket_create(host, port);
    ts_begin = cnts.jiffies;

    while (!sh->break && session.s->state != TCP_SOCKET_STATE_ESTABLISHED && !(TELNET_CONNECTION_TIMED_OUT)) {
        Sleep(1);
    }

    if (sh->break) {
        session.s->close();
        return 1;
    }

    if (TELNET_CONNECTION_TIMED_OUT) {
        Stdio.WriteLine(sh, "Connection timed out.\n");
        session.s->close();
        return 1;
    }

    StrPrint(buf, "Connected to %s.\n", host);
    Stdio.WriteLine(sh, buf);

    Spawn(&@telnet_session, &session);

    while (session.s->state != TCP_SOCKET_STATE_CLOSED) {
        @telnet_handle_input(sh, session.s);
        Sleep(1);
    }

    Stdio.WriteLine(sh, "Connection closed by foreign host.\n");
    return 1;
}