#include "client.hpp"
#include "client_state.hpp"
#include "server.hpp"
#include "irc_cmd.hpp"
#include "common.hpp"
#include <assert.h>


FdTable<Client*> Client::client_fds;

const char* const Client::dummy_host = "0.0.0.0";


Client::Client(int _fd, const ip_str_t _ip): fd(_fd), last_seen(NOW), msg_stream_in(_fd), msg_stream_out(_fd), host(strndup(_ip, strrchr(_ip,':')-_ip)), addr(strdup(_ip)), irc_state(new ClientState(this)) {
    assert(client_fds[fd] == NULL);
    client_fds[fd] = this;

    Poll::getInst()->add(fd, &Client::handle, EVENT_IN);
}


void Client::createInst(int _fd, const ip_str_t _ip) {
    assert(!log_ctx || !strcmp(log_ctx, _ip));
    log_ctx = _ip;
    new Client(_fd, _ip);
    log_ctx = NULL;
}


Client::~Client() {
    LOG("closing.");

    Server::getInst()->disconnect(this, NULL); // if not yet done with msg

    Poll::getInst()->del(fd);
    io_handlers.close_handler(fd);

    assert(client_fds[fd] == this);
    client_fds[fd] = NULL;

    free((void*)host);
    free((void*)addr);
    delete irc_state;
}


void Client::killall(const char* msg) {
    for (unsigned i=0; i<client_fds.size; i++) {
        if (client_fds[i]) {
            Client* c = client_fds[i];
            Server::notice(c, msg);
            delete c;
        }
    }
}


void Client::handle(int fd, event_t events) {
    Client* inst = client_fds[fd];
    assert(inst);
    assert(!log_ctx || !strcmp(log_ctx, inst->addr));
    log_ctx = inst->addr;
    inst->handle(events);
    log_ctx = NULL;
}


void Client::handle(event_t event) {
    // kick/ping clients that seem idle?
    if (event == EVENT_TOUT) {
        if (last_seen < NOW-SOCK_TIMEOUT) { // idle too long
            LOG("kicking idle client");
            Server::getInst()->disconnect(this, "*** Timeout.");
            delete this;
            return;
        } else { // poll/tickle-timeout only -> send PING
            if (!Server::getInst()->handle(this, NULL)) {
                delete this;
            }
            return;
        }
    }

    bool ok = true;
    if (event & EVENT_IN) {
        last_seen = NOW;
        ok &= handle_in();
    }
    if ((event & EVENT_OUT) || (event & EVENT_IN)) { // try to flush
        ok &= handle_out();
    }
    if ((event & EVENT_CLOSE) && ok) {
        LOG("client closed connection");
        ok = false;
    }
    if (!ok) {
        delete this;
    }
}


bool Client::handle_in() {
    const ssize_t rlen = msg_stream_in.io();
    if (rlen < 0) {
        return false;
    }

    msg_t* msg = NULL;
    while (true) {
        ssize_t len = msg_stream_in.pop(&msg);
        if (!len) {
            break; // cannot parse yet
        } else if (len == -1) {
            Server::notice(this, ":Client-Bug: IRC Message parsing error");
            return false;
        }

        Command* cmd = Command::parse(msg);
        if (!cmd) {
            Server::notice(this, ":IRC Command parsing error");
            return false;
        }
        if (!Server::getInst()->handle(this, cmd)) {
            delete cmd;
            return false;
        }
        delete cmd;
    }

    if (rlen == 0) { // eof check after popping above
        return false; // TODO: proper shutdown() support needed?
    }

    return true;
}


bool Client::handle_out() {
    bool rv = msg_stream_out.io();
    Poll::getInst()->mod(fd, &Client::handle, EVENT_IN|(msg_stream_out.empty()? EVENT_NONE: EVENT_OUT));
    return rv;
}


bool Client::send(msg_t* buf, size_t len) {
    bool rv = msg_stream_out.send(buf, len);
    Poll::getInst()->mod(fd, &Client::handle, EVENT_IN|(msg_stream_out.empty()? EVENT_NONE: EVENT_OUT));
    return rv;
}