#include "client_state.hpp"
#include "nickserv.hpp"
#include "irc_cmd.hpp"
#include <assert.h>


#define IRC_MAX_NICK_LEN 16
#define IRC_MAX_AWAY_LEN 128
#define IRC_MAX_REAL_LEN 64


bool ClientState::is_nick(const char* buf) {
    /*
     * <nick>       ::= <letter> { <letter> | <number> | <special> }
     * <number>     ::= '0' ... '9'
     * <special>    ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}'
     * however, we will only support '_' and '-'
     */
    size_t len = 0;
    const char* p = buf;
    if (!is_letter(*p)) {
        return false;
    }
    do {
        if (!is_letter(*p) && !is_number(*p) && *p != '_' && *p != '-') {
            return false;
        }
        ++p;
        ++len;
        if (len > IRC_MAX_NICK_LEN) {
            return false;
        }
    } while (*p);

    if (Command::is_command(buf)) {
        return false;
    }

    return true;
}


std::map<std::string, Client*> ClientState::nicks; // TODO: store direct pointers to client_state_t nicks (need comparison/searching then)


ClientState::ClientState(Client* c):
    client(c),
    has_pass(false), is_oper(false),
    nickserv_reg(false), nickserv_ident(false),
    nick(NULL), user(NULL), real(NULL), away(NULL) {
}


ClientState::~ClientState() {
    if (nick) {
        nicks.erase(std::string(nick));
        messages.push((msg_t*)nick);
    }
    if (user) messages.push((msg_t*)user);
    if (real) messages.push((msg_t*)real);
    if (away) messages.push((msg_t*)away);
}


Client* ClientState::nick_find(const char* n) {
    if (!is_nick(n)) return NULL;
    std::map<std::string, Client*>::iterator it = nicks.find(std::string(n));
    return (it == nicks.end())? NULL: it->second;
}


bool ClientState::nick_set(const char* n) {
    if (!is_nick(n)) {
        return false;
    }
    const std::string nn(n);

    NickServ* ns = NickServ::getInst();
    if (nick) {
        if (nickserv_reg || ns->is_reg(n)) {
            return false; // no moving away from registered nick, no switching to
        }
    }

    if (nicks.find(nn) != nicks.end()) {
        return false;
    }

    if (nick) {
        nicks.erase(std::string(nick));
        messages.push((msg_t*)nick);
    }

    nick = strcpy((char*)messages.pop(), n);
    nicks.insert(std::pair<std::string, Client*>(nn, client));
    nickserv_reg = ns->is_reg(nick);
    nickserv_ident = false;
    return true;
}


bool ClientState::user_set(const char* u, const char* r) {
    if (user || real || !u || !r) {
        return false;
    }

    if (!is_nick(u)) {
        return false;
    }
    if (!*r || strlen(r) > IRC_MAX_REAL_LEN) {
        return false;
    }

    user = strcpy((char*)messages.pop(), u);
    real = strcpy((char*)messages.pop(), r);
    return true;
}


bool ClientState::away_set(const char* a) {
    if (a && strlen(a) > IRC_MAX_AWAY_LEN) {
        return false;
    }

    if (away) {
        messages.push((msg_t*)away);
    }
    if (!a || !*a) {
        away = NULL;
    } else {
        away = strcpy((char*)messages.pop(), a);
    }

    return true;
}


const char* ClientState::prefix_get(bool details, bool reveal, const char* n) const {
    static char rv[IRC_MAX_NICK_LEN + 1 + 1 + IRC_MAX_NICK_LEN + 1 + sizeof(ip_str_t) + 1];

    assert(nick); // XXX:
    if (!nick && !n) {
        return "*";
    }
    if (!details || !user) {
        return n ?: nick;
    }

    strcpy(rv, n ?: nick);
    strcat(rv, "!");
    if (!nickserv_ident) strcat(rv, "~");
    strcat(rv, user);
    strcat(rv, "@");
    strcat(rv, reveal? client->host: Client::dummy_host);

    return rv;
}


const char* ClientState::mode_get() const {
    if (is_oper) return "o";
    if (nickserv_ident) return "v"; // voice. or h for half-op?
    return "";
}


const char* ClientState::state_get() const {
    if (is_oper) return "@";
    if (nickserv_ident) return "+"; // voice. or % for half-op?
    return "";
}