#include "Server.hpp"

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);
}

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)
{
    send(fd, msg.c_str(), msg.size(), 0);
}

void Server::removeUser(int fd)
{
    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)
        throw std::runtime_error(strerror(errno));
    fcntl(newFd, F_SETFL, O_NONBLOCK);
    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++)
        dispatch(fd, messages[i]);
}

void Server::run(void)
{
    while (true)
    {
        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)
                handleRecv(_fds[i].fd);
        }
    }
}

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);
}

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);
    }
}

void Server::handleNick(int fd, const IrcMessage &msg)
{
    std::string newNick = msg.param(0);
    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 ;
        }
    }
    _users[fd].nick = newNick;
    _users[fd].nickOk = true;
}

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;
}