#include "Server.hpp"

extern volatile sig_atomic_t g_stop;

void Server::start(void)
{
    _serverFd = socket(AF_INET, SOCK_STREAM, 0);
    if (_serverFd == -1)
        throw std::runtime_error(strerror(errno));
    int opt = 1;
    if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
        {
        close(_serverFd);
        throw std::runtime_error(strerror(errno));
    }
    if (fcntl(_serverFd, F_SETFL, O_NONBLOCK) == -1)
    {
        close(_serverFd);
        throw std::runtime_error(strerror(errno));
    }
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family      = AF_INET;
    addr.sin_port        = htons(_port);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(_serverFd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
        {
        close(_serverFd);
        throw std::runtime_error(strerror(errno));
    }
    if (listen(_serverFd, SOMAXCONN) == -1)
        {
        close(_serverFd);
        throw std::runtime_error(strerror(errno));
    }
    struct pollfd pollfd;
    pollfd.fd = _serverFd;
    pollfd.events  = POLLIN;
    pollfd.revents = 0;
    _fds.push_back(pollfd);
    std::cout << "FT_IRC server startded on " << _port << " Port." << std::endl << "The password is \"" << _password << "\"." << std::endl << "Ctrl+c for shutdown the server" << std::endl;
}

ConnectedUser::ConnectedUser(void) : fd(-1), passOk(false), nickOk(false), userOk(false)
{
}

ConnectedUser::ConnectedUser(int fd) : fd(fd), passOk(false), nickOk(false), userOk(false)
{
}

bool ConnectedUser::isRegistered(void) const
{
    return (passOk && nickOk && userOk);
}

Server::Server(int port, const std::string &password)
{
    _port = port;
    _password = password;
    _serverFd = -1;
}

Server::Server(const Server &other)
{
    *this = other;
}

Server &Server::operator=(const Server &other)
{
     if (this != &other)
     {
        _serverFd = other._serverFd;
        _port = other._port;
        _password = other._password;
        _fds = other._fds;
        _users = other._users;
     }
     return *this;
}

Server::~Server(void)
{
    for (size_t i = 0; i < _fds.size(); i++)
    {
        close(_fds[i].fd);
    }
}

void Server::sendReply(int fd, const std::string &msg)
{
    size_t total = 0;
    while (total < msg.size())
    {
        ssize_t s = send(fd, msg.c_str() + total, msg.size() - total, 0);
        if (s <= 0)
            return ;
        total += static_cast<size_t>(s);
    }
}

void Server::removeUser(int fd)
{
    std::map<std::string, Channel>::iterator it = _channels.begin();
    while (it != _channels.end())
    {
        it->second.removeMember(fd);
        it->second.removeOperator(fd);
        it->second.removeInvited(fd);
        if (it->second.memberCount() == 0)
            _channels.erase(it++);
        else
            ++it;
    }
    close(fd);
    _users.erase(fd);
    for (size_t i = 0; i < _fds.size(); i++)
    {
        if (_fds[i].fd == fd)
        {
            _fds.erase(_fds.begin() + i);
            break ;
        }
    }
}

void Server::acceptNew(void)
{
    int newFd = accept(_serverFd, NULL, NULL);
    if (newFd == -1)
        return ;
    fcntl(newFd, F_SETFL, O_NONBLOCK);
    int flag = 1;
    setsockopt(newFd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));
    struct pollfd pfd;
    pfd.fd = newFd;
    pfd.events = POLLIN;
    pfd.revents = 0;
    _fds.push_back(pfd);
    _users.insert(std::make_pair(newFd, ConnectedUser(newFd)));
}

void Server::handleRecv(int fd)
{
    char buf[512];
    int n;   
    n = recv(fd, buf, sizeof(buf), 0);
    if (n == 0)
    {
        removeUser(fd);
        return ;
    }
    else if (n == -1)
    {
        if (errno == EAGAIN || errno == EWOULDBLOCK)
            return ;
        else
        {
            removeUser(fd);
            return ;
        }   
    }
    _users[fd].parseBuffer.append(buf, n);
    std::vector<IrcMessage> messages = _users[fd].parseBuffer.extractMessages();
    for (size_t i = 0; i < messages.size(); i++)
    { 
        if (_users.find(fd) == _users.end())
            break ;
        dispatch(fd, messages[i]);
    }
}

void Server::run(void)
{
    while (!g_stop)
    {
        int ret = poll(_fds.data(), _fds.size(), -1);
        if (ret == -1)
            break ;
        for (size_t i = 0; i < _fds.size(); i++)
        {
            if (i == 0 && _fds[0].revents & POLLIN)
                acceptNew();
            else if (_fds[i].revents & (POLLHUP | POLLERR))
            {
                int fd = _fds[i].fd;
                removeUser(fd);
                i--;
            }
            else if (_fds[i].revents & POLLIN)
            {
                size_t before = _fds.size();
                handleRecv(_fds[i].fd);
                if (_fds.size() < before)
                    i--;
            }

        }
    }
}

void Server::dispatch(int fd, const IrcMessage &msg)
{
    if (!msg.isValid())
        return ;
    const std::string &cmd = msg.getCommand();
    if (CommandValidator::needsRegistration(cmd) && !_users[fd].isRegistered())
    {
        sendReply(fd, "451 :You have not registered\r\n");
        return ;
    }
    else if (!CommandValidator::hasEnoughParams(msg))
    {
        sendReply(fd, "461 " + cmd + " :Not enough parameters\r\n");
        return ;
    }
    else if (cmd == "PASS")
        handlePass(fd, msg);
    else if (cmd == "NICK")
        handleNick(fd, msg);
    else if (cmd == "USER")
        handleUser(fd, msg);
    else if (cmd == "PING")
        handlePing(fd, msg);
    else if (cmd == "QUIT")
        handleQuit(fd, msg);
    else if (cmd == "JOIN")
        handleJoin(fd, msg);
    else if (cmd == "PART")
        handlePart(fd, msg);
    else if (cmd == "PRIVMSG")
        handlePrivmsg(fd, msg);
    else if (cmd == "KICK")
        handleKick(fd, msg);
    else if (cmd == "INVITE")
        handleInvite(fd, msg);
    else if (cmd == "TOPIC")
        handleTopic(fd, msg);
    else if (cmd == "MODE")
        handleMode(fd, msg);
}

void Server::handlePass(int fd, const IrcMessage &msg)
{
    if (msg.param(0) == _password)
        _users[fd].passOk = true;
    else
    {
        sendReply(fd, "464 :Password incorrect\r\n");
        removeUser(fd);
    }
}

static bool isNickChar(char c, bool first)
{
    if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
        return (true);
    if (c == '[' || c == ']' || c == '\\' || c == '`'
        || c == '_' || c == '^' || c == '{' || c == '|' || c == '}')
        return (true);
    if (!first && ((c >= '0' && c <= '9') || c == '-'))
        return (true);
    return (false);
}

static bool isValidNick(const std::string &nick)
{
    if (nick.empty())
        return (false);
    for (size_t i = 0; i < nick.size(); ++i)
    {
        if (!isNickChar(nick[i], i == 0))
            return (false);
    }
    return (true);
}

void Server::handleNick(int fd, const IrcMessage &msg)
{
    std::string newNick = msg.param(0);
    if (!isValidNick(newNick))
    {
        sendReply(fd, "432 * " + newNick + " :Erroneous nickname\r\n");
        return ;
    }
    std::map<int, ConnectedUser>::iterator it = _users.begin();
    for (; it != _users.end(); it++)
    {
        if (it->second.nick == newNick && it->first != fd)
        {
            sendReply(fd, "433 * " + newNick + " :Nickname is already in use\r\n");
            return ;
        }
    }
    bool wasRegistered = _users[fd].isRegistered();
    std::string oldNick = _users[fd].nick;
    _users[fd].nick = newNick;
    _users[fd].nickOk = true;
    if (wasRegistered && oldNick != newNick)
    {
        std::string notice = ":" + oldNick + "!" + _users[fd].username
            + "@localhost NICK " + newNick + "\r\n";
        std::map<int, bool> seen;
        seen[fd] = true;
        for (std::map<std::string, Channel>::iterator cit = _channels.begin();
            cit != _channels.end(); ++cit)
        {
            if (!cit->second.hasMember(fd))
                continue ;
            const std::vector<int> &mem = cit->second.getMembers();
            for (size_t i = 0; i < mem.size(); ++i)
                seen[mem[i]] = true;
        }
        for (std::map<int, bool>::iterator sit = seen.begin(); sit != seen.end(); ++sit)
            sendReply(sit->first, notice);
    }
}

void Server::handleUser(int fd, const IrcMessage &msg)
{
    if (_users[fd].isRegistered() == true)
        return ;
    _users[fd].username = msg.param(0);
    _users[fd].realname = msg.param(3);
    _users[fd].userOk = true;
    if (_users[fd].isRegistered())
    {
        sendReply(fd, ":server 001 " + _users[fd].nick + " :Welcome to the IRC server\r\n");
        sendReply(fd, ":server 002 " + _users[fd].nick + " :Your host is server\r\n");
    }
}

void Server::handlePing(int fd, const IrcMessage &msg)
{
    sendReply(fd, "PONG :" + msg.param(0) + "\r\n");
}

void Server::handleQuit(int fd, const IrcMessage &msg)
{
    removeUser(fd);
    (void)msg;
}

void Server::broadcastToChannel(const std::string &chanName, const std::string &msg, int exceptFd)
{
    Channel &chan = _channels[chanName];
    const std::vector<int> &members = chan.getMembers();
    for (std::vector<int>::const_iterator it = members.begin(); it != members.end(); ++it)
    {
        if (*it != exceptFd)
            sendReply(*it, msg);
    }
}

void Server::handleJoin(int fd, const IrcMessage &msg)
{
    std::string chanName = msg.param(0);
    if (!chanName.empty() && chanName[0] != '#')
    {
        sendReply(fd, "403 " + chanName + " :No such channel\r\n");
        return ;
    }
    if (_channels.find(chanName) == _channels.end())
        _channels.insert(std::make_pair(chanName, Channel(chanName)));
    Channel &chan = _channels[chanName];
    if (chan.isInviteOnly() && !chan.isInvited(fd))
    {
        sendReply(fd, "473 " + chanName + " :Cannot join channel (+i)\r\n");
        return ;
    }
    if (chan.getKey() != "" && (msg.paramCount() < 2 || msg.param(1) != chan.getKey()))
    {
        sendReply(fd, "475 " + chanName + " :Cannot join channel (+k)\r\n");
        return ;
    }
    if (chan.getUserLimit() > 0 && chan.memberCount() >= chan.getUserLimit())
    {
        sendReply(fd, "471 " + chanName + " :Cannot join channel (+l)\r\n");
        return ;
    }
    if (chan.hasMember(fd))
        return ;
    chan.addMember(fd);
    if (chan.memberCount() == 1)
        chan.addOperator(fd);
    std::string msg_add = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost JOIN " + chanName + "\r\n";
    broadcastToChannel(chanName, msg_add, -1);
    if (chan.getTopic() != "")
        sendReply(fd, "332 " + _users[fd].nick + " " + chanName + " :" + chan.getTopic() + "\r\n");
}

void Server::handlePart(int fd, const IrcMessage &msg)
{
    std::string chanName = msg.param(0);
    if (_channels.find(chanName) == _channels.end())
    {
        sendReply(fd, "403 " + chanName + " :No such channel\r\n");
        return ;
    }
    Channel &chan = _channels[chanName];
    if (!chan.hasMember(fd))
    {
        sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
        return ;
    }
    std::string msg_rm = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost PART " + chanName + "\r\n";
    broadcastToChannel(chanName, msg_rm, -1);
    chan.removeMember(fd);
    chan.removeOperator(fd);
    chan.removeInvited(fd);
    if (chan.memberCount() == 0)
        _channels.erase(chanName);
}

void Server::handlePrivmsg(int fd, const IrcMessage &msg)
{
    std::string target = msg.param(0);
    std::string text = msg.param(1);
    std::string mess = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost PRIVMSG " + target + " :" + text + "\r\n";
    if (target[0] == '#')
    {
        if (_channels.find(target) == _channels.end())
        {
            sendReply(fd, "403 " + target + " :No such channel\r\n");
            return ;
        }
        Channel &chan = _channels[target];
        if (!chan.hasMember(fd))
        {
            sendReply(fd, "404 " + target + " :Cannot send to channel\r\n");
            return ;
        }
        broadcastToChannel(target, mess, fd);
    }
    else
    {
        for (std::map<int, ConnectedUser>::iterator it = _users.begin(); it != _users.end(); ++it)
        {
            if (it->second.nick == target)
            {
                sendReply(it->first, mess);
                return;
            }
        }
        sendReply(fd, "401 " + target + " :No such nick\r\n");
    }
}

void Server::handleKick(int fd, const IrcMessage &msg)
{
    std::string chanName = msg.param(0);
    std::string targetNick = msg.param(1);
    if (_channels.find(chanName) == _channels.end())
    {
        sendReply(fd, "403 " + chanName + " :No such channel\r\n");
        return ;
    }
    Channel &chan = _channels[chanName];
    if (!chan.hasMember(fd))
    {
        sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
        return ;
    }
    if (!chan.isOperator(fd))
    {
        sendReply(fd, "482 " + chanName + " :You're not channel operator\r\n");
        return ;
    }
    std::map<int, ConnectedUser>::iterator it = _users.begin();
    int targetFd = -1;
    for (; it != _users.end(); it++)
    {
        if (it->second.nick == targetNick)
        {
            targetFd = it->first;
            break ;
        }
    }
    if (targetFd == -1)
    {
        sendReply(fd, "401 " + targetNick + " :No such nick\r\n");
        return ;
    }
    if (!chan.hasMember(targetFd))
    {
        sendReply(fd, "441 " + targetNick + " " + chanName + " :They aren't on that channel\r\n");
        return ;
    }
    std::string kick_msg =  ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost KICK " + chanName + " " + targetNick + "\r\n";
    broadcastToChannel(chanName, kick_msg, -1);
    chan.removeMember(targetFd);
    chan.removeOperator(targetFd);
    chan.removeInvited(targetFd);
}

void Server::handleInvite(int fd, const IrcMessage &msg)
{
    std::string targetNick = msg.param(0);
    std::string chanName = msg.param(1);
    if (_channels.find(chanName) == _channels.end())
    {
        sendReply(fd, "403 " + chanName + " :No such channel\r\n");
        return ;
    }
    Channel &chan = _channels[chanName];
    if (!chan.hasMember(fd))
    {
        sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
        return ;
    }
    if (!chan.isOperator(fd))
    {
        sendReply(fd, "482 " + chanName + " :You're not channel operator\r\n");
        return ;
    }
    std::map<int, ConnectedUser>::iterator it = _users.begin();
    int targetFd = -1;
    for (; it != _users.end(); it++)
    {
        if (it->second.nick == targetNick)
        {
            targetFd = it->first;
            break ;
        }
    }
    if (targetFd == -1)
    {
        sendReply(fd, "401 " + targetNick + " :No such nick\r\n");
        return ;
    }
    chan.addInvited(targetFd);
    std::string invite_msg = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost INVITE " + targetNick + " " + chanName + "\r\n";
    sendReply(targetFd, invite_msg);
}

void Server::handleTopic(int fd, const IrcMessage &msg)
{
    std::string chanName = msg.param(0);
    if (_channels.find(chanName) == _channels.end())
    {
        sendReply(fd, "403 " + chanName + " :No such channel\r\n");
        return ;
    }
    Channel &chan = _channels[chanName];
    if (!chan.hasMember(fd))
    {
        sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
        return ;
    }
    if (msg.paramCount() == 1)
    {
        if (chan.getTopic() != "")
            sendReply(fd, "332 " + _users[fd].nick + " " + chanName + " :" + chan.getTopic() + "\r\n");
        else
            sendReply(fd, "331 " + _users[fd].nick + " " + chanName + " :No topic is set\r\n");
        return ;
    }
    if (chan.isTopicRestricted() && !chan.isOperator(fd))
    {
        sendReply(fd, "482 " + chanName + " :You're not channel operator\r\n");
        return ;
    }
    chan.setTopic(msg.param(1));
    std::string bd_msg = ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost TOPIC " + chanName + " :" + chan.getTopic() + "\r\n";
    broadcastToChannel(chanName, bd_msg, -1);
}

void Server::handleMode(int fd, const IrcMessage &msg)
{
    std::string chanName = msg.param(0);
    std::string modeStr = msg.param(1);
    if (modeStr.size() < 2 || (modeStr[0] != '+' && modeStr[0] != '-'))
        return ;
    if (chanName.empty() || chanName[0] != '#')
        return ;
    if (_channels.find(chanName) == _channels.end())
    {
        sendReply(fd, "403 " + chanName + " :No such channel\r\n");
        return ;
    }
    Channel &chan = _channels[chanName];
    if (!chan.hasMember(fd))
    {
        sendReply(fd, "442 " + chanName + " :You're not on that channel\r\n");
        return ;
    }
    if (!chan.isOperator(fd))
    {
        sendReply(fd, "482 " + chanName + " :You're not channel operator\r\n");
        return ;
    }
    bool adding = (modeStr[0] == '+');
    char mode = modeStr[1];
    if (mode == 'i')
        chan.setInviteOnly(adding);
    else if (mode == 't')
        chan.setTopicRestricted(adding);
    else if (mode == 'k')
        if (adding)
            if (msg.paramCount() >= 3)
                chan.setKey(msg.param(2));
            else
                return ;
        else
            chan.setKey("");
    else if (mode == 'o')
    {
        if (msg.paramCount() >= 3)
        {
            std::map<int, ConnectedUser>::iterator it = _users.begin();
            int targetFd = -1;
            for (; it != _users.end(); it++)
            {
                if (it->second.nick ==  msg.param(2))
                {
                    targetFd = it->first;
                    break ;
                }
            }
            if (targetFd == -1)
            {
                sendReply(fd, "401 " + msg.param(2) + " :No such nick\r\n");
                return ;
            }
            if (adding)
                chan.addOperator(targetFd);
            else
                chan.removeOperator(targetFd);
        }
        else
            return ;
    }
    else if (mode == 'l')
    {
        if (adding)
        {
            if (msg.paramCount() >= 3)
                chan.setUserLimit((size_t)atoi(msg.param(2).c_str()));
            else
                return ;
        }
        else
            chan.setUserLimit(0);
    }
    std::string mod_msg =  ":" + _users[fd].nick + "!" + _users[fd].username + "@localhost MODE " + chanName + " " + modeStr + "\r\n";
    broadcastToChannel(chanName, mod_msg, -1);
}