#include "irc_cmd.hpp"
#include "common.hpp"
#include <assert.h>


#define to_upper(c)  (((c)>='a' && (c)<='z')? (c)-32: (c))


#define command_type_a_init(c, min, max, msg) {CMD_##c, STRINGIFY(c), min, (unsigned)max, msg}
static const Command::command_info_t command_type_a[] = {
    command_type_a_init(PASS,     1,  1, false),
    command_type_a_init(CAP,      0, -1, false),
    command_type_a_init(NICK,     1,  1, false),
    command_type_a_init(USER,     4,  4, false),
    command_type_a_init(OPER,     2,  2, false),
    command_type_a_init(MOTD,     0,  1, false),
    command_type_a_init(JOIN,     1,  2, false),
    command_type_a_init(WHO,      1,  1, false),
    command_type_a_init(LIST,     0,  0, false),
    command_type_a_init(AWAY,     0,  1, false),
    command_type_a_init(MODE,     1,  2, false),
    command_type_a_init(TOPIC,    1,  2, false),
    command_type_a_init(PING,     0,  1, false),
    command_type_a_init(PONG,     0, -1, false),
    command_type_a_init(PRIVMSG,  2,  2, false),
    command_type_a_init(PART,     1,  2, false),
    command_type_a_init(QUIT,     0,  1, false),
    command_type_a_init(NICKSERV, 1,  3, true),
    {CMD_UNKNOWN, NULL,           1,  1, false}
};


static const Command::command_info_t* cmd_find(const char* s) {
    const Command::command_info_t* cmds = command_type_a;
    do {
        if (strcasecmp(s, cmds->s) == 0) {
            return cmds;
        }
        cmds++;
    } while (cmds->t != CMD_UNKNOWN);
    return cmds;
}


bool Command::is_command(const char* nick) {
    return cmd_find(nick)->t != CMD_UNKNOWN; // even if not from_privmsg
}


Command::Command(const command_info_t* t): cleanup(NULL), type(t), params(), nparams(0) {
}


Command::~Command() {
    if (cleanup) {
        messages.push(cleanup);
    }
}


Command* Command::parse(msg_t* mbuf) {
    char* buf = (char*)mbuf;
    const command_info_t* type = parse_cmd(buf);
    if (!type) { // parsing err
        LOG("invalid command string '%s'", (const char*)mbuf); // XXX: assumes buf gets tokenized and only includes valid chars
        messages.push(mbuf);
        return NULL;
    }
    Command* cmd = new Command(type);

    if (type->t == CMD_UNKNOWN) {
        cmd->nparams = 1;
        cmd->params[0] = (char*)mbuf;
        LOG("unknown command '%s'", cmd->params[0]);
    } else {
        if (!cmd->parse_params(buf)) {
            LOG("cannot parse params for %s", type->s);
            delete cmd;
            messages.push(mbuf);
            return NULL;
        }
    }

    if (type->t == CMD_PRIVMSG && cmd->nparams == 2) {
        const command_info_t* ntype = cmd_find(cmd->params[0]);
        if (ntype->from_privmsg) {
            buf = cmd->params[1];
            delete cmd;
            cmd = new Command(ntype);
            if (!cmd->parse_params(buf)) {
                LOG("cannot parse params for %s -> %s", type->s, cmd->type->s);
                delete cmd;
                messages.push(mbuf);
                return NULL;
            }
        }
    }

    cmd->cleanup = mbuf;
    return cmd;
}


const Command::command_info_t* Command::parse_cmd(char*& buf) {
    char* s = buf;
    while (is_letter(*buf)) {
        *buf = to_upper(*buf); // still needed?
        ++buf;
    }

    if (*buf == '\0') {
        // without args
    } else if (*buf == ' ') {
        *buf = '\0';
        do { ++buf; } while (*buf == ' ');
    } else {
        *buf = '\0'; // terminate after valid chars in any case
        return NULL;
    }

    return cmd_find(s);
}


bool Command::parse_params(char* buf) {
    while (*buf) {
        if (nparams == IRC_MAX_CMD_PARAMS) {
            return false;
        }

        if (*buf == ':') { // special trailing arg until end supporting spaces
            if (buf[1]) { // not if empty
                params[nparams++] = buf+1;
            }
            break;
        }

        params[nparams++] = buf;
        do {
            if (*buf == ' ') {
                *buf = '\0';
                do {
                    ++buf;
                } while (*buf == ' ');
                break;
            }
            ++buf;
        } while (*buf);
    }
    return true;
}