#include "irc_std.hpp"
#include "irc_msg.hpp"
#include "client_state.hpp"
#include "config.hpp"
#include <assert.h>
#include <stdarg.h>


static bool setstr(char*& b, size_t& l, char c) {
    if (!l) return false;
    *(b++) = c;
    --l;
    return true;
}


static bool setstr(char*& b, size_t& l, const char* c, size_t cl) {
    if (cl > l) return false;
    memcpy(b, c, cl);
    b += cl;
    l -= cl;
    return true;
}


static bool setstr(char*& b, size_t& l, const char* c) {
    return setstr(b, l, c, strlen(c));
}


static bool setstr(char*& b, size_t& l, const char* fmt, va_list args) {
    int rv = vsnprintf(b, l, fmt, args);
    if (rv < 0 || (size_t)rv >= l) {
        return false;
    }
    b += rv;
    l -= rv;
    return true;
}


bool raw_reply_msg(Client* dst, const char* str, size_t len) {
    if (len > messages.len()) {
        return dst->send(NULL, 0);
    }
    msg_t* msg = messages.pop();
    memcpy(msg, str, len);
    return dst->send(msg, len);
}


bool raw_reply_fmt(Client* dst, const char* fmt, ...) {
    msg_t* msg = messages.pop();
    char* buf = (char*)msg;
    size_t len = messages.len();

    va_list args;
    va_start(args, fmt);
    if (!setstr(buf, len, fmt, args)) {
        va_end(args);
        messages.push(msg);
        return dst->send(NULL, 0);
    }
    va_end(args);

    return dst->send(msg, buf-(char*)msg);
}


static bool reply_splittable(Client* dst, msg_t* prefix, size_t prefix_len, const char* suffix, size_t suffix_len) {
    if (prefix_len + 2 >= IRC_MAX_MSG_LEN) {
        messages.push(prefix);
        return dst->send(NULL, 0);
    }

    while (prefix_len + suffix_len + 2 > IRC_MAX_MSG_LEN) {
        msg_t* msg = messages.pop();
        memcpy(msg, prefix, prefix_len);

        size_t l = IRC_MAX_MSG_LEN - prefix_len - 2;
        assert(l);
        assert(l < suffix_len);
        memcpy((char*)msg + prefix_len, suffix, l);
        suffix += l;
        suffix_len -= l;

        if (!dst->send(msg, prefix_len + l)) {
            messages.push(prefix);
            return dst->send(NULL, 0);
        }
    }

    memcpy((char*)prefix + prefix_len, suffix, suffix_len);
    return dst->send(prefix, prefix_len + suffix_len);
}


bool raw_reply_fmt_long(Client* dst, const char* str, const char* fmt, ...) {
    msg_t* msg = messages.pop();
    char* buf = (char*)msg;
    size_t len = messages.len();

    va_list args;
    va_start(args, fmt);
    if (!setstr(buf, len, fmt, args)) {
        va_end(args);
        messages.push(msg);
        return dst->send(NULL, 0);
    }
    va_end(args);

    if (str) {
        if (!setstr(buf, len, " :", 2)) {
            messages.push(msg);
            return dst->send(NULL, 0);
        }
        return reply_splittable(dst, msg, buf-(char*)msg, str, strlen(str));
    }

    return dst->send(msg, buf-(char*)msg);
}


static char* srv_reply_pre(char* buf, size_t& len, const char* prefix, const char* code, const Client* nick) {
    if (!setstr(buf, len, ':') || !setstr(buf, len, prefix)) {
        return NULL;
    }

    if (code) {
        if (!setstr(buf, len, ' ') || !setstr(buf, len, code)) {
            return NULL;
        }
    }

    if (!setstr(buf, len, ' ') || !setstr(buf, len, nick->irc_state->nick ?: "*")) {
        return NULL;
    }

    return buf;
}


bool srv_reply_msg(Client* dst, const char* code, const char* str, size_t slen) {
    msg_t* msg = messages.pop();
    char* buf = (char*)msg;
    size_t len = messages.len();

    buf = srv_reply_pre(buf, len, config.server_name.str, code, dst);
    if (!buf) {
        messages.push(msg);
        return dst->send(NULL, 0);
    }

    if (str) {
        if (!setstr(buf, len, ' ') || !setstr(buf, len, str, slen)) {
            messages.push(msg);
            return dst->send(NULL, 0);
        }
    }

    return dst->send(msg, buf-(char*)msg);
}


bool srv_reply_fmt(Client* dst, const char* code, const char* fmt, ...) {
    msg_t* msg = messages.pop();
    char* buf = (char*)msg;
    size_t len = messages.len();

    buf = srv_reply_pre(buf, len, config.server_name.str, code, dst);
    if (!buf) {
        messages.push(msg);
        return dst->send(NULL, 0);
    }

    if (fmt) {
        va_list args;
        va_start(args, fmt);
        if (!setstr(buf, len, ' ') || !setstr(buf, len, fmt, args)) {
            va_end(args);
            messages.push(msg);
            return dst->send(NULL, 0);
        }
        va_end(args);
    }

    return dst->send(msg, buf-(char*)msg);
}


bool cli_reply_msg(Client* dst, const Client* nick, const char* str, size_t slen) {
    msg_t* msg = messages.pop();
    char* buf = (char*)msg;
    size_t len = messages.len();

    if (!setstr(buf, len, ':') || !setstr(buf, len, nick->irc_state->prefix_get(true, dst == nick || dst->irc_state->is_oper))) { // e.g. pidgin segfaults in irc_msg_join() when only using the nick
        messages.push(msg);
        return dst->send(NULL, 0);
    }

    if (str) {
        if (!setstr(buf, len, ' ') || !setstr(buf, len, str, slen)) {
            messages.push(msg);
            return dst->send(NULL, 0);
        }
    }

    return dst->send(msg, buf-(char*)msg);
}


bool cli_reply_fmt(Client* dst, const Client* nick, const char* fmt, ...) {
    msg_t* msg = messages.pop();
    char* buf = (char*)msg;
    size_t len = messages.len();

    if (!setstr(buf, len, ':') || !setstr(buf, len, nick->irc_state->prefix_get(true, dst == nick || dst->irc_state->is_oper))) {
        messages.push(msg);
        return dst->send(NULL, 0);
    }

    if (fmt) {
        va_list args;
        va_start(args, fmt);
        if (!setstr(buf, len, ' ') || !setstr(buf, len, fmt, args)) {
            va_end(args);
            messages.push(msg);
            return dst->send(NULL, 0);
        }
        va_end(args);
    }

    return dst->send(msg, buf-(char*)msg);
}