#include "Channel.hpp"
#include "errcodes.hpp"
#include "Server.hpp"
#include "log.hpp"
#include "split.hpp"
#include <cstdlib>
#include <sstream>

Channel::Channel(std::string const& name, Client& client) :
    _clients(),
    _op_clients(),
    _name(name),
    _topic(),
    _fInviteOnly(false),
    _fTopicRestric(false),
    _keyword(),
    _userLimit(0)
{
    _op_clients.insert(&client);
    if (addClient(client)) {
        return;
    }
}

Channel::Channel(Channel const& src) :
    _clients(src._clients),
    _op_clients(src._clients),
    _name(src._name),
    _topic(src._topic),
    _fInviteOnly(src._fInviteOnly),
    _fTopicRestric(src._fTopicRestric),
    _keyword(src._keyword),
    _userLimit(src._userLimit)
{}

bool
Channel::addClient(Client& client)
{
    if (_clients.insert(&client).second) {
        std::set<Client*>::iterator it = _clients.begin();
        for (; it != _clients.end(); ++it) {
            (*it)->sendMsg(client.nick, "JOIN", _name);
        }
        if (!_topic.empty()) {
            if (client.sendCode(RPL_TOPIC, _name, _topic)) {
                removeClient(client);
                return false;
            }
        }
        std::string names;
        for (it = _clients.begin(); it != _clients.end(); ++it) {
            if (isOp(**it)) {
                names += '@';
            }
            names += (*it)->nick + ' ';
        }
        if (client.sendCode(RPL_NAMREPLY, _name, names)) {
            removeClient(client);
            return false;
        }
        return true;
    }
    return false;
}

bool
Channel::addOp(Client& client)
{
    if (_clients.find(&client) == _clients.end()) {
        /* client to op not in channel */
        return false;
    }
    if (_op_clients.insert(&client).second) {
        /* client is now op */
        LOG << client.nick << " is op" << std::endl;
        _op_clients.insert(&client);
        return true;
    }
    /* cringe shit lol */
    return false;
}

bool
Channel::removeClient(Client& client)
{
    removeOp(client);
    return (_clients.erase(&client) == 1);
}

bool
Channel::removeOp(Client &client)
{
    return (_op_clients.erase(&client) == 1);
}

bool
Channel::partClient(Client& client, std::string const& reason)
{
    if (_clients.find(&client) == _clients.end()) {
        return false;
    }

    std::set<Client*>::iterator it;
    for (it = _clients.begin(); it != _clients.end(); ++it) {
        (*it)->sendMsg(client.nick, "PART " + _name, reason);
    }
    return removeClient(client);
}

bool
Channel::kickClient(Client& client, Client& victim, std::string const& reason)
{
    if (_clients.find(&client) == _clients.end()) {
        return false;
    }

    std::set<Client*>::iterator it;
    for (it = _clients.begin(); it != _clients.end(); ++it) {
        (*it)->sendMsg(client.nick, "KICK " + _name + ' ' + victim.nick, reason);
    }
    return removeClient(victim);
}

size_t
Channel::numClients() const
{
    return _clients.size();
}

int
Channel::sendMessage(std::string const& msg, Client const* client) const
{
    std::string const sender = client ? client->nick : "";
    std::set<Client*>::iterator it = _clients.begin();
    for (; it != _clients.end(); ++it) {
        if (client == *it) {
            continue;
        }
        (*it)->sendMsg(sender, "PRIVMSG " + _name, msg);
    }
    return 0;
}

int
Channel::sendNotice(std::string const& msg, Client const& client) const
{
    std::set<Client*>::iterator it = _clients.begin();
    for (; it != _clients.end(); ++it) {
        if (client == **it) {
            continue;
        }
        (*it)->sendMsg(client.nick, "NOTICE " + _name, msg);
    }
    return 0;
}

int
Channel::sendTopic(Client& client) const
{
    if (!isOnChannel(client)) {
        return 0;
    }

    if (_topic.empty()) {
        return client.sendCode(RPL_NOTOPIC, _name, "No topic is set");
    } else {
        return client.sendCode(RPL_TOPIC, _name, _topic);
    }
}

int
Channel::setTopic(Client& client, std::string const& newTopic)
{
    if (_fTopicRestric && !isOp(client)) {
        return client.sendCode(ERR_CHANOPRIVSNEEDED, _name,
                "You're not channel operator");
    }
    _topic = newTopic;
    return sendTopic(client);
}

int
Channel::setMode(Server& server, Client& client, string_vector const& args)
{
    if (!isOnChannel(client)) {
        return client.sendCode(ERR_NOTONCHANNEL, _name,
                "You're not on that channel");
    }
    if (!isOp(client)) {
        return client.sendCode(ERR_CHANOPRIVSNEEDED, _name,
                "You're not channel operator");
    }

    bool sign = true;
    size_t argToCheck = 2;
    std::string const& modes = args[1];

    string_vector update(1);

    for (size_t i = 0; i < modes.length(); ++i) {
        const char c = modes[i];
        if (c == 'o' || ((c == 'k' || c == 'l') && sign)) {
            if (args.size() <= argToCheck) {
                continue;
            }
        }

        switch (c) {
        case '-':
            sign = false;
            if (update[0].length() &&
                   (update[0][update[0].length() - 1] == '-' ||
                   update[0][update[0].length() - 1] == '+'))
            {
                    update[0].erase(update[0].length() - 1);
            }
            update[0].push_back(c);
            break;
        case '+':
            sign = true;
            if (update[0].length() &&
                   (update[0][update[0].length() - 1] == '-' ||
                   update[0][update[0].length() - 1] == '+'))
            {
                    update[0].erase(update[0].length() - 1);
            }
            update[0].push_back(c);
            break;
        case 'i':
            if (_fInviteOnly != sign) {
                update[0].push_back(c);
            }
            _fInviteOnly = sign;
            break;
        case 't':
            if (_fInviteOnly != sign) {
                update[0].push_back(c);
            }
            _fTopicRestric = sign;
            break;
        case 'o': {
            Client* user = server.findClient(args[argToCheck++]);
            if (!user) {
                if (client.sendCode(ERR_NOSUCHNICK, args[argToCheck - 1], "No such nick (UwU~)")) {
                    return -1;
                }
                break;
            }
            if (!isOnChannel(*user)) {
                if (client.sendCode(ERR_USERNOTINCHANNEL, args[argToCheck - 1] + ' ' + _name,
                                    "They aren't on that channel"))
                {
                    return -1;
                }
                break;
            }
            if (sign) {
                if (!isOp(*user)) {
                    update[0].push_back(c);
                    update.push_back(args[argToCheck - 1]);
                }
                addOp(*user);
            } else {
                if (isOp(*user)) {
                    update[0].push_back(c);
                    update.push_back(args[argToCheck - 1]);
                }
                removeOp(*user);
            }
            break;
        }
        case 'k':
            _keyword = sign ? args[argToCheck++] : "";
            update[0].push_back(c);
            if (sign) {
                update.push_back(args[argToCheck - 1]);
            }
            break;
        case 'l':
            if (sign) {
                const int n = std::atoi(args[argToCheck++].c_str());
                if (n > 0) {
                    update[0].push_back(c);
                    update.push_back(args[argToCheck - 1]);
                    _userLimit = n;
                }
            } else {
                if (_userLimit) {
                    update[0].push_back(c);
                }
                _userLimit = 0;
            }
            break;
        default: {
            const char cs[] = {c, 0};
            if (client.sendCode(ERR_UNKNOWNMODE, cs,
                                "is unknown mode char to " + _name))
            {
                return -1;
            }
            break;
        }
        }
    }
    if (update[0].length() &&
           (update[0][update[0].length() - 1] == '-' ||
           update[0][update[0].length() - 1] == '+'))
    {
            update[0].erase(update[0].length() - 1);
    }

    if (!update[0].length()) {
        return 0;
    }
    for (size_t i = 1; i < update.size(); ++i) {
        update[0] += ' ' + update[i];
    }

    std::set<Client*>::iterator it = _clients.begin();
    for (; it != _clients.end(); ++it) {
        (*it)->sendMsg(client.nick, "MODE " + _name + " " + update[0], "");
    }
    return 0;
}

int
Channel::sendMode(Client& client)
{
    string_vector modes(1);
    if (_fInviteOnly) {
        modes[0] += 'i';
    }
    if (_fTopicRestric) {
        modes[0] += 't';
    }
    if (!_keyword.empty()) {
        modes[0] += 'k';
        if (isOnChannel(client)) {
            modes.push_back(_keyword);
        }
    }
    if (_userLimit != 0) {
        modes[0] += 'l';
        std::stringstream str;
        str << _userLimit;
        modes.push_back(str.str());
    }

    std::string out = _name + " +" + modes[0];
    for (size_t i = 1; i < modes.size(); ++i) {
        out += ' ' + modes[i];
    }
    return client.sendCode(RPL_CHANNELMODEIS, out, "");
}

std::string const&
Channel::getName() const
{
    return _name;
}

size_t
Channel::getUserLimit() const
{
    return _userLimit;
}

bool
Channel::isInviteOnly() const
{
    return _fInviteOnly;
}

 std::string const&
 Channel::getKeyword() const
{
    return _keyword;
}

bool
Channel::areOnChannel(Client& c1, Client& c2) const
{
    if (_clients.find(&c1) != _clients.end()) {
        if (_clients.find(&c2) != _clients.end()) {
            return true;
        }
    }
    return false;
}

bool
Channel::isOnChannel(Client& c1) const
{
    if (_clients.find(&c1) != _clients.end()) {
            return true;
    }
    return false;
}

bool
Channel::isOp(Client &client) const
{
    return (_op_clients.find(&client) != _op_clients.end());
}

bool
Channel::isChannelEmpty() const
{
    if (_clients.empty() && _op_clients.empty()) {
        return true;
    }
    return false;
}

Channel&
Channel::operator=(Channel const& rhs)
{
    if (this == &rhs) {
        return *this;
    }

    _clients = rhs._clients;
    _op_clients = rhs._op_clients;
    _topic = rhs._topic;
    _fInviteOnly = rhs._fInviteOnly;
    _fTopicRestric = rhs._fTopicRestric;
    _keyword = rhs._keyword;
    _userLimit = rhs._userLimit;

    return *this;
}

bool
Channel::operator==(Channel const& rhs) const
{
    return _name == rhs._name;
}

bool
Channel::operator==(std::string const& name) const
{
    return _name == name;
}
