#include "Server.hpp"
#include "Client.hpp"
#include "Message.hpp"
#include "error.hpp"
#include "errcodes.hpp"
#include "log.hpp"
#include <cstddef>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <algorithm>
#include <unistd.h>
#include <string>
#include <cstring>

Server::Server(int port, const std::string& keyword) :
    _port(port),
    _keyword(keyword),
    _efd(-1),
    _socket(-1),
    _dead(false),
    _clients(),
    _channels()
{}

Server::~Server()
{
    _dead = true;

    if (_socket >= 0) {
        close(_socket);
        _socket = -1;
    }

    if (_efd >= 0) {
        close(_efd);
        _socket = -1;
    }
}

int
Server::init()
{
    _efd = epoll_create('U'+'w'+'U');
    if (_efd < 0) {
        error("epoll_create");
        return -1;
    }

    _socket = socket(AF_INET, SOCK_STREAM, 0);
    if (_socket < 0) {
        error("socket");
        return -1;
    }

    int option = 1;
    if (setsockopt(_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option)) < 0) {
       error("setsockopt");
       return -1;
    }

    struct sockaddr_in addr = {};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(_port);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(_socket, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        error("bind");
        return -1;
    }

    if (listen(_socket, 128) < 0) {
        error("listen");
        return -1;
    }

    return _addFd(_socket);
}

int
Server::_addFd(int fd) const
{
    epoll_event event = {};
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    if (epoll_ctl(_efd, EPOLL_CTL_ADD, fd, &event)) {
        error("epoll_ctl");
        return -1;
    }
    return 0;
}

int
Server::wait()
{
    epoll_event event = {};
    const int ev = epoll_wait(_efd, &event, 1, -1);
    if (ev < 0) {
        error("epoll_wait");
        return -1;
    }

    const int fd = event.data.fd;
    if (fd == _socket) {
        if (_accept(fd)) {
            return -1;
        }
        return 0;
    }

    return _receiveMsg(fd);
}

int
Server::_accept(int fd)
{
    _clients.push_back(Client(*this));
    Client& client = _clients.back();

    if (client.init(fd)) {
        _clients.pop_back();
        return -1;
    }

    return _addFd(client.getFd());
}

int
Server::_receiveMsg(int fd)
{
    std::list<Client>::iterator client;
    for (client = _clients.begin(); client != _clients.end(); ++client) {
        if (client->getFd() == fd) {
            break;
        }
    }
    if (client == _clients.end()) {
        LOG << "no client has this fd" << std::endl;
        return -1;
    }

    if (client->recv()) {
        return 0;
    }

    while (_processMsg(*client) == 0) {}

    return 0;
}

int
Server::_processMsg(Client &client)
{
    if (!client.queuedMsg()) {
        return 1;
    }

    const Message msg(client, client.pollMsg());
    LOG << msg << std::endl;

    if (msg.getCmd().empty()) {
        return 0;
    }

    const std::string& cmd = msg.getCmd();

    if (!client.getPass()) {
        if (cmd != "PASS" || msg.getArgs().empty() || msg.getArgs()[0] != _keyword) {
            client.fatalSendCode(ERR_PASSWDMISMATCH);
            return 1;
        }
        client.setPass();
        return 0;
    }

    if (cmd == "PASS") {
        return client.sendCode(ERR_ALREADYREGISTRED);
    }

    if (cmd == "NICK") {
        return _cmdNICK(msg);
    }
    if (cmd == "USER") {
        return _cmdUSER(msg);
    }

    if (!client.getRegistered() || client.nick.empty()) {
        return client.sendCode(ERR_NOTREGISTERED);
    }

    if (cmd == "QUIT") {
        return _cmdQUIT(msg);
    }
    if (cmd == "PART") {
        return _cmdPART(msg);
    }

    if (cmd == "JOIN") {
        return _cmdJOIN(msg);
    }

    if (cmd == "PRIVMSG") {
        return _cmdPRIVMSG(msg);
    }

    if (cmd == "NOTICE") {
        return _cmdNOTICE(msg);
    }

    if (cmd == "KICK") {
        return _cmdKICK(msg);
    }

    if (cmd == "TOPIC") {
        return _cmdTOPIC(msg);
    }

    if (cmd == "PING") {
        return _cmdPING(msg);
    }

    if (cmd == "MODE") {
        return _cmdMODE(msg);
    }

    if (cmd == "INVITE") {
        return _cmdINVITE(msg);
    }

    if (cmd == "MOTD") {
        return _cmdMOTD(msg);
    }


    client.sendCode(ERR_UNKNOWNCOMMAND, cmd, "Unknown command");
    return 1;
}

void
Server::removeClient(Client &client)
{
    _clients.remove(client);
}

void
Server::quitClient(Client& client)
{
    std::list<Client>::iterator it;
    for (it = _clients.begin(); it != _clients.end(); ++it) {
        if (client == *it) {
            continue;
        }
        for (size_t i = 0; i < _channels.size(); ++i) {
            if (_channels[i].areOnChannel(client, *it)) {
                it->sendMsg(client.nick, "QUIT", "Client closed connection");
                goto next_client;
            }
        }
next_client:;
    }
    for (size_t i = 0; i < _channels.size(); ++i) {
        _channels[i].removeClient(client);
        if (_channels[i].isChannelEmpty()) {
            _channels.erase(_channels.begin() + i);
            std::list<Client>::iterator itC;
            for (itC = _clients.begin(); itC != _clients.end(); ++itC) {
                itC->isInvited(_channels[i]);
            }
        }
    }
    removeClient(client);
}

Client*
Server::findClient(const std::string& nick)
{
    std::list<Client>::iterator it = std::find(_clients.begin(), _clients.end(), nick);
    if (it != _clients.end()) {
        return &*it;
    }
    return NULL;
}

int
Server::_welcomeClient(Client& client)
{
    if (client.getWelcomed()) {
        return 0;
    }

    if (client.sendCode(RPL_WELCOME, client.nick, "Welcome to our IRC server bozo")) {
        return -1;
    }
    client.setWelcomed();
    return 0;
}

bool
Server::isLive() const
{
    return !_dead;
}
