#include "channel.hpp"
#include "config.hpp"
#include "client_state.hpp"
#include "irc_std.hpp"
#include "common.hpp"
#include <assert.h>


#define IRC_MAX_TOPIC_LEN 128
#define IRC_MAX_CHANNEL_LEN 16


static bool is_channel(const char* buf, size_t len) {
    if (len < 2 || len > IRC_MAX_CHANNEL_LEN || *buf != '#') {
        return false;
    }
    do {
        buf++;
        len--;
    } while (len && (is_letter(*buf) || is_number(*buf) || *buf == '_' || *buf == '-' || *buf == '.'));
    return (len == 0);
}


std::map<std::string, Channel*> Channel::channels;


Channel::Channel(const char* _name, bool pers, bool oper, bool ident): chantopic(strdup(_name)), persistent(pers), oper_only(oper), ident_only(ident), name(strdup(_name)) {
    assert(channels.find(std::string(name)) == channels.end());
    channels.insert(std::pair<std::string, Channel*>(std::string(name), this));
    LOG("created channel %s", name);
}


Channel::~Channel() {
    LOG("destroying channel %s", name);

    assert(clients.empty()); // notify clients thus not needed

    assert(channels.find(std::string(name)) != channels.end());
    channels.erase(std::string(name));

    free((void*)chantopic);
    free((void*)name);
}


bool Channel::createInsts() {
    for (std::vector<const char*>::const_iterator it = config.channel.strs.begin(); it != config.channel.strs.end(); ++it) {
        char* c = strdup(*it);
        bool admin = false;
        bool ident = false;
        if (*c == '@') {
            *c = '#';
            admin = true;
        } else if (*c == '+') {
            *c = '#';
            ident = true;
        }

        char* t = strchr(c, ':');
        if (t) {
            *(t++) = '\0';
        }

        Channel* channel = Channel::getInst(c, false, true, admin, ident);
        if (!channel) {
            LOG("cannot create pre-defined channel '%s'", c); // but go on
        } else if (t && *t && !channel->topic(t)) {
            LOG("cannot set topic for pre-defined channel '%s'", c); // but go on
        }

        free(c);
    }
    return true;
}


void Channel::killall() {
    while (!channels.empty()) {
        std::map<std::string, Channel*>::iterator it = channels.begin();
        delete it->second;
    }
}


Channel* Channel::getInst(const char* name, bool only_if_exists, bool pers, bool oper, bool ident) {
    if (!is_channel(name, strlen(name))) {
        return NULL;
    }
    std::map<std::string, Channel*>::iterator it = channels.find(std::string(name));
    if (it != channels.end()) {
        return it->second;
    } else {
        if (only_if_exists) {
            return NULL;
        }
        return new Channel(name, pers, oper, ident);
    }
}


void Channel::get_peers(const Client* client, std::set<Client*>& peers) { // TODO: should maintain this?
    for (std::set<Channel*>::iterator chit = client->irc_state->channels.begin(); chit != client->irc_state->channels.end(); ++chit) {
        for (std::set<Client*>::iterator clit = (*chit)->clients.begin(); clit != (*chit)->clients.end(); ++clit) {
            if (*clit != client) peers.insert(*clit); // ensures uniqueness
        }
    }
}


bool Channel::is_peer(const Client* a, const Client* b) {
    for (std::set<Channel*>::iterator chit = a->irc_state->channels.begin(); chit != a->irc_state->channels.end(); ++chit) {
        for (std::set<Client*>::iterator clit = (*chit)->clients.begin(); clit != (*chit)->clients.end(); ++clit) {
            if (*clit == b) return true;
        }
    }
    return false;
}


bool Channel::join(Client* client) {
    // check if permissive
    if (clients.find(client) != clients.end()) {
        return true; // already in here - ignore
    }
    if (oper_only && !client->irc_state->is_oper) {
        return srv_reply_fmt(client, REPLYFMT(ERR_INVITEONLYCHAN), name, "oper only");
    }
    if (ident_only && !client->irc_state->nickserv_ident) { // opers are ident, too
        return srv_reply_fmt(client, REPLYFMT(ERR_INVITEONLYCHAN), name, "nickserv only");
    }

    // let join
    clients.insert(client);
    client->irc_state->channels.insert(this);

    // acknowledge/replay and broadcast JOIN
    for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) {
        (void)cli_reply_fmt(*it, client, "JOIN :%s", name); // ok if not all get it, as error will be cached there
        if (*it == client) continue;
        const char* mode = client->irc_state->mode_get();
        while (*mode) { // also directly broadcast that its an op etc.
            (void)cli_reply_fmt(*it, client, "MODE %s +%c %s", name, *mode, client->irc_state->nick);
            mode++;
        }
    }

    // topic
    if (!srv_reply_fmt(client, REPLYFMT(RPL_TOPIC), name, chantopic)) return false; // RPL_TOPICSETBY?

    // names. TODO: could send multiple ones in one line
    for (std::set<Client*>::const_iterator it = clients.begin(); it != clients.end(); it++) {
        if (!srv_reply_fmt(client, REPLYFMT(RPL_NAMREPLY), name, (*it)->irc_state->state_get(), (*it)->irc_state->nick)) return false;
    }
    return srv_reply_fmt(client, REPLYFMT(RPL_ENDOFNAMES), name);
}


bool Channel::mode(Client* client) {
    const char* mode = client->irc_state->mode_get();
    for (std::set<Channel*>::iterator chan_it = client->irc_state->channels.begin(); chan_it != client->irc_state->channels.end(); ++chan_it) {
        Channel* channel = *chan_it;
        for (std::set<Client*>::iterator cli_it = channel->clients.begin(); cli_it != channel->clients.end(); ++cli_it) {
            const char* m = mode;
            while (*m) { // also directly broadcast that its an op etc.
                if (!cli_reply_fmt(*cli_it, client, "MODE %s +%c %s", channel->name, *m, client->irc_state->nick)) {
                    if (*cli_it == client) return false;
                }
                m++;
            }
        }
    }
    return true;
}


void Channel::cleanup() {
    if (clients.empty() && !persistent) {
        delete this;
    }
}


void Channel::remove(Client* client) {
    std::set<Client*>::iterator it = clients.find(client);
    assert(it != clients.end());
    if (it == clients.end()) {
        return;
    }

    clients.erase(it);
    client->irc_state->channels.erase(this);

    cleanup();
}


bool Channel::part(Client* client, const char* msg) {
    if (clients.find(client) == clients.end()) {
        return srv_reply_fmt(client, REPLYFMT(ERR_NOTONCHANNEL), name);
    }
    for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) { // so include part'ing client
        (void)cli_reply_fmt(*it, client, "PART %s :%s", name, msg ?: "/part");
    }
    remove(client);
    return true;
}


void Channel::disconnect(Client* client, const char* msg) {
    std::set<Client*> peers;
    get_peers(client, peers);
    for (std::set<Client*>::iterator it = peers.begin(); it != peers.end(); ++it) {
        (void)cli_reply_fmt(*it, client, "QUIT :%s", msg);
    }

    for (std::set<Channel*>::iterator it = client->irc_state->channels.begin(); it != client->irc_state->channels.end();) {
        (*(it++))->remove(client); // might delete channel, but the iterator should be still valid
    }
}


bool Channel::nickchange(const char* old, Client* client) {
    if (!raw_reply_fmt(client, ":%s NICK %s", client->irc_state->prefix_get(false, true, old), client->irc_state->nick)) {
        return false;
    }

    std::set<Client*> peers;
    get_peers(client, peers);
    for (std::set<Client*>::iterator it = peers.begin(); it != peers.end(); ++it) {
        (void)raw_reply_fmt(*it, ":%s NICK %s", client->irc_state->prefix_get(true, (*it)->irc_state->is_oper, old), client->irc_state->nick); // TODO: some reply_multi that re-uses buffer?
    }

    return true;
}


bool Channel::topic(const char* t) {
    if (!t || !*t || strlen(t) > IRC_MAX_TOPIC_LEN) {
        return false;
    }
    if (chantopic) {
        free((void*)chantopic);
    }
    chantopic = strdup(t);
    return true;
}


bool Channel::topic(Client* client) {
    if (clients.find(client) == clients.end()) {
        return srv_reply_fmt(client, REPLYFMT(ERR_NOTONCHANNEL), name);
    } else {
        return srv_reply_fmt(client, REPLYFMT(RPL_TOPIC), name, chantopic);
    }
}


bool Channel::topic(Client* client, const char* msg) {
    if (clients.find(client) == clients.end()) {
        return srv_reply_fmt(client, REPLYFMT(ERR_NOTONCHANNEL), name);
    }
    if (!client->irc_state->is_oper) {
        return srv_reply_fmt(client, REPLYFMT(ERR_CHANOPRIVSNEEDED), name);
    }
    if (topic(msg)) {
        for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) {
            (void)cli_reply_fmt(*it, client, "TOPIC %s :%s", name, chantopic);
        }
        return true;
    } else {
        return srv_reply_fmt(client, REPLYFMT(ERR_NEEDMOREPARAMS), "topic"); // XXX: better error here?
    }
}


bool Channel::who(Client* client) {
    if (clients.find(client) == clients.end()) { // not from outside
        return srv_reply_fmt(client, REPLYFMT(ERR_NOTONCHANNEL), name);
    }
    for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) {
        if (!srv_reply_fmt(client, REPLYFMT(RPL_WHOREPLY),
            name,
            (*it)->irc_state->nickserv_ident? "": "~",
            (*it)->irc_state->user,
            (client == *it || client->irc_state->is_oper)? (*it)->host: Client::dummy_host, // hide IPs
            config.server_name.str,
            (*it)->irc_state->nick,
            (*it)->irc_state->away? 'G': 'H',
            (*it)->irc_state->state_get(),
            (*it)->irc_state->real
        )) return false;
    }
    return srv_reply_fmt(client, REPLYFMT(RPL_ENDOFWHO), name);
}


bool Channel::list(Client* client) {
    if (!srv_reply_msg(client, REPLYMSG(RPL_LISTSTART))) return false;
    for (std::map<std::string, Channel*>::const_iterator cit = channels.begin(); cit != channels.end(); ++cit) {
        const Channel* c = cit->second;
        if (!srv_reply_fmt(client, REPLYFMT(RPL_LIST), c->name, c->clients.size(), c->chantopic)) return false;
    }
    return srv_reply_msg(client, REPLYMSG(RPL_LISTEND));
}


bool Channel::mode_get(Client* client) {
    char modes[16];
    char* m = modes;
    *(m++) = '+';
    *(m++) = 'n'; // no external messages
    *(m++) = 't'; // only channel operators may set the channel topic
    if (oper_only) {
        *(m++) = 'O'; // only allows IRC operators to join
    }
    if (ident_only) {
        *(m++) = 'r'; // regonly: only registered users may join the channel (XXX: and/or 'R'?)
    }
    if (persistent) {
        *(m++) = 'P'; // channel does not disappear when empty
    }
#ifdef USE_OPENSSL
    if (atoi(config.ssl.str)) {
        *(m++) = 'z'; // only allows clients connected via secure connections to join
        *(m++) = 'Z'; // only clients connected via secure connections are on the channel
    }
#endif
    *m = '\0';
    return srv_reply_fmt(client, REPLYFMT(RPL_CHANNELMODEIS), name, modes);
}


bool Channel::privmsg(Client* client, const char* msg) {
    if (clients.find(client) == clients.end()) {
        return srv_reply_fmt(client, REPLYFMT(ERR_CANNOTSENDTOCHAN), name);
    }

    for (std::set<Client*>::iterator it = clients.begin(); it != clients.end(); it++) {
        if (*it == client) {
            continue; // not for sender himself?
        }
        (void)raw_reply_fmt_long(*it, msg, ":%s PRIVMSG %s", client->irc_state->prefix_get(true, (*it)->irc_state->is_oper), name);
    }
    return true;
}