/*
 * Copyright (C) 2002-2004 Morten Brix Pedersen <morten@wtf.dk>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
 */

#include <config.h>
#include <iostream>
#include <cstring>
#include <functional>
#include <algorithm>
#include <unistd.h>
#include "ServerConnection.h"
#include "LostIRCApp.h"
#include "Commands.h"

using Glib::ustring;
using std::vector;

namespace algo
{

  struct deletePointer : public std::unary_function<ChannelBase*, void>
  {
      deletePointer() { }
      void operator() (ChannelBase* x) {
          delete x;
      }
  };

}

ServerConnection::ServerConnection(const ustring& host, const ustring& nick, int port, bool doconnect)
    : _parser(this)
{
    Session.nick = nick;
    Session.servername = host;
    Session.host = host;
    Session.port = port;
    Session.isConnected = false;
    Session.hasRegistered = false;
    Session.isAway = false;
    Session.endOfMotd = false;
    Session.sentLagCheck = false;
    Session.realname = App->options.realname;

    _socket.on_host_resolved.connect(sigc::mem_fun(*this, &ServerConnection::on_host_resolved));
    _socket.on_connected.connect(sigc::mem_fun(*this, &ServerConnection::on_connected));
    _socket.on_data_pending.connect(sigc::mem_fun(*this, &ServerConnection::onReadData));
    _socket.on_error.connect(sigc::mem_fun(*this, &ServerConnection::on_error));

    App->fe->newTab(this);

    if (doconnect)
          connect();
}

ServerConnection::~ServerConnection()
{
    for_each(Session.channels.begin(), Session.channels.end(), algo::deletePointer());
}

void ServerConnection::connect(const ustring &host, int port, const ustring& pass)
{
    Session.servername = host;
    Session.host = host;
    Session.password = pass;
    Session.port = port;

    connect();
}

void ServerConnection::reconnect()
{
    connect(Session.servername, Session.port, Session.password);
}

void ServerConnection::doCleanup()
{
    // called when we get disconnected or connect to a new server, need to
    // clean various things up.
    Session.isConnected = false;
    Session.isConnecting = false;
    Session.hasRegistered = false;
    Session.isAway = false;
    Session.endOfMotd = false;
    Session.sentLagCheck = false;
    _bufpos = 0;

    if (signal_connection.connected())
          signal_connection.disconnect();

    for_each(Session.channels.begin(), Session.channels.end(), algo::deletePointer());
    Session.channels.clear();

    _socket.disconnect();
}

void ServerConnection::disconnect()
{
    #ifdef DEBUG
    App->log << "ServerConnection::disconnect()" << std::endl;
    #endif

    doCleanup();

    FE::emit(FE::get(CLIENTMSG) << _("Disconnected."), FE::ALL, this);
    App->fe->disconnected(this);
}

void ServerConnection::connect()
{
    #ifdef DEBUG
    App->log << "ServerConnection::connect()" << std::endl;
    #endif

    doCleanup();
    App->fe->disconnected(this);

    Session.isConnecting = true;

    FE::emit(FE::get(CONNECTING) << Session.host << Session.port, FE::CURRENT, this);

    _socket.connect(Session.host, Session.port);
}


void ServerConnection::on_error(const char *msg)
{
    FE::emit(FE::get(ERRORMSG) << ustring(_("Failed connecting: ")) + Util::convert_to_utf8(msg), FE::CURRENT, this);
    disconnect();
}

void ServerConnection::on_host_resolved()
{
    FE::emit(FE::get(CLIENTMSG) << _("Resolved host. Connecting.."), FE::CURRENT, this);
}

void ServerConnection::on_connected(Glib::IOCondition cond)
{
    Session.isConnecting = false;
    Session.isConnected = true;
    App->fe->connected(this);

    // The only purpose of this function is to register us to the server
    // when we are able to write
    FE::emit(FE::get(CLIENTMSG) << _("Connected. Logging in..."), FE::CURRENT, this);

    if (cond & Glib::IO_OUT) {
        char hostname[256]; // FIXME: should this be located somewhere else?
        gethostname(hostname, sizeof(hostname) - 1);

        if (!Session.password.empty())
              sendPass(Session.password);

        sendNick(Session.nick);
        sendUser(App->options.ircuser, hostname, Session.host, Session.realname);
    }
}

void ServerConnection::onReadData()
{
    #ifdef DEBUG
    App->log << "Serverconnection::onReadData(): reading.." << std::endl;
    #endif

    try {

        char buf[4096];
        int received = 0;
        if (_socket.receive(buf, 4095, received)) {

            for (int i = 0; i < received; ++i) {
                if (buf[i] == '\r') {
                    // Skip.
                } else if (buf[i] == '\n') {
                    // Send line to parser
                    _tmpbuf[_bufpos] = '\0';
                    std::string str(_tmpbuf, _bufpos);
                    Glib::ustring str_utf8 = Util::convert_to_utf8(str);
                    _parser.parseLine(str_utf8);
                    _bufpos = 0;
                } else {
                    if (_bufpos < 520) {
                        _tmpbuf[_bufpos] = buf[i];
                        // Ignore line if it's too long.
                        _bufpos++;
                    }
                }
            }
        }

    } catch (SocketException &e) {
        FE::emit(FE::get(ERRORMSG) << ustring(_("Failed to receive: ")) + Util::convert_to_utf8(e.what()), FE::ALL, this);
        disconnect();
        addReconnectTimer();
    } catch (SocketDisconnected &e) {
        disconnect();
        addReconnectTimer();
    }
}


bool ServerConnection::autoReconnect()
{
    #ifdef DEBUG
    App->log << "ServerConnection::autoReconnect(): reconnecting." << std::endl;
    #endif
    connect();
    return false;
}

bool ServerConnection::connectionCheck()
{
    if (Session.sentLagCheck) {
        // disconnected! last lag check was never replied to
        #ifdef DEBUG
        App->log << "ServerConnection::connectionCheck(): disconnected." << std::endl;
        #endif
        disconnect();
        connect();

        return false;
    } else {
        #ifdef DEBUG
        App->log << "ServerConnection::connectionCheck(): still on" << std::endl;
        #endif
        sendPing();
        Session.sentLagCheck = true;
        return true;
    }
}

void ServerConnection::addConnectionTimerCheck()
{
    signal_connection = Glib::signal_timeout().connect(
            sigc::mem_fun(*this, &ServerConnection::connectionCheck),
            30000);
}

void ServerConnection::addReconnectTimer()
{
    if (!signal_autoreconnect.connected())
          signal_autoreconnect = Glib::signal_timeout().connect(
                  sigc::mem_fun(*this, &ServerConnection::autoReconnect),
                  2000);
}

void ServerConnection::removeReconnectTimer()
{
    if (signal_autoreconnect.connected())
          signal_autoreconnect.disconnect();
}


bool ServerConnection::sendPong(const ustring& crap)
{
    ustring msg("PONG :" + crap + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendPing(const ustring& crap)
{
    ustring msg("PING LAG" + crap + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendUser(const ustring& nick, const ustring& localhost, const ustring& remotehost, const ustring& name)
{
    ustring realname = (name.empty() ? nick : name);

    ustring msg("USER " + nick + " " + localhost + " " + remotehost + " :" + realname + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendNick(const ustring& nick)
{
    if (Session.isConnected && Session.hasRegistered) {
        ustring msg("NICK " + nick + "\r\n");

        return _socket.send(msg);
    } else if (Session.isConnected && !Session.hasRegistered) {
        Session.nick = nick;
        ustring msg("NICK " + nick + "\r\n");

        return _socket.send(msg);
    } else {
        Session.nick = nick;
        return false;
    }
}

bool ServerConnection::sendPass(const ustring& pass)
{
    ustring msg("PASS " + pass + "\r\n");
    return _socket.send(msg);
}

bool ServerConnection::sendVersion(const ustring& to)
{
    #ifndef WIN32
    ustring s(LostIRCApp::uname_info.sysname);
    ustring r(LostIRCApp::uname_info.release);
    ustring m(LostIRCApp::uname_info.machine);
    #else
    ustring s("Windows");
    ustring r("");
    ustring m("");
    #endif
    ustring vstring("LostIRC "VERSION" on " + s + " " + r + " [" + m + "]");
    ustring msg("NOTICE " + to + " :\001VERSION " + vstring + "\001\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendMsg(const ustring& to, const ustring& message, bool sendToGui)
{
    ustring msg("PRIVMSG " + to + " :" + message + "\r\n");

    if (sendToGui) {
        ChannelBase *chan = findChannel(to);
        if (!chan)
              chan = findQuery(to);

        if (chan)
              FE::emit(FE::get(PRIVMSG_SELF) << Session.nick << message, *chan, this);
        else
              FE::emit(FE::get(PRIVMSG_SELF) << Session.nick << message, FE::CURRENT, this);
    }

    return _socket.send(msg);
}

bool ServerConnection::sendNotice(const ustring& to, const ustring& message)
{
    ustring msg("NOTICE " + to + " :" + message + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendJoin(const ustring& chan)
{
    ustring msg("JOIN " + chan + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendPart(const ustring& chan, const ustring& message)
{
    ustring msg;
    if (!message.empty())
          msg = "PART " + chan + " :" + message + "\r\n";
    else
          msg = "PART " + chan + "\r\n";

    return _socket.send(msg);
}

bool ServerConnection::sendKick(const ustring& chan, const ustring& nick, const ustring& kickmsg)
{
    ustring msg("KICK " + chan + " " + nick + " :" + kickmsg + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendWhois(const ustring& params)
{
    ustring msg("WHOIS " + params + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendList(const ustring& params)
{
    ustring msg("LIST " + params + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendQuit(const ustring& quitmsg)
{
    if (Session.isConnected) {
        ustring msg;
        if (quitmsg.size() < 1) {
            msg = "QUIT\r\n";
        } else {
            msg = "QUIT :" + quitmsg + "\r\n";
        }

        return _socket.send(msg);
    }
    return true;
}

bool ServerConnection::sendMode(const ustring& params)
{
    ustring msg("MODE " + params + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendCtcp(const ustring& to, const ustring& params)
{
    ustring msg("PRIVMSG " + to + " :\001" + params + "\001\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendCtcpNotice(const ustring& to, const ustring& params)
{
    ustring msg("NOTICE " + to + " :\001" + params + "\001\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendAway(const ustring& params)
{
    ustring msg("AWAY :" + params + "\r\n");
    Session.awaymsg = params;

    return _socket.send(msg);
}

bool ServerConnection::sendAdmin(const ustring& params)
{
    ustring msg("ADMIN " + params + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendWhowas(const ustring& params)
{
    ustring msg("WHOWAS " + params + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendInvite(const ustring& to, const ustring& params)
{
    ustring msg("INVITE " + to + " " + params + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendTopic(const ustring& chan, const ustring& topic)
{
    ustring msg;
    if (topic.empty())
          msg = "TOPIC " + chan + "\r\n";
    else
          msg = "TOPIC " + chan + " :" + topic + "\r\n";

    return _socket.send(msg);
}

bool ServerConnection::sendBanlist(const ustring& chan)
{
    ustring msg("MODE " + chan + " +b\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendMe(const ustring& to, const ustring& message)
{
    ustring msg("PRIVMSG " + to + " :\001ACTION " + message + "\001\r\n");

    ChannelBase *chan = findChannel(to);
    if (!chan)
          chan = findQuery(to);

    if (chan)
          FE::emit(FE::get(ACTION) << Session.nick << message, *chan, this);
    else
          FE::emit(FE::get(ACTION) << Session.nick << message, FE::CURRENT, this);

    return _socket.send(msg);
}

bool ServerConnection::sendWho(const ustring& mask)
{
    ustring msg("WHO " + mask + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendNames(const ustring& chan)
{
    ustring msg("NAMES " + chan + "\r\n");

    return _socket.send(msg);
}

bool ServerConnection::sendOper(const ustring& login, const ustring& password)
{
    ustring msg("OPER " + login + ' ' + password + "\r\n" );

    return _socket.send(msg);
}

bool ServerConnection::sendWallops(const ustring& message)
{
    ustring msg("WALLOPS :" + message + "\r\n" );

    return _socket.send(msg);
}

bool ServerConnection::sendKill(const ustring& nick, const ustring& reason)
{
    ustring msg("KILL " + nick + " :" + reason + "\r\n" );

    return _socket.send(msg);
}

bool ServerConnection::sendRaw(const ustring& text)
{
    ustring msg(text + "\r\n");

    return _socket.send(msg);
}

void ServerConnection::addChannel(const ustring& n)
{
    Channel *c = new Channel(n);
    Session.channels.push_back(c);
}

void ServerConnection::addQuery(const ustring& n)
{
    Query *c = new Query(n);
    Session.channels.push_back(c);
}

bool ServerConnection::removeChannel(const ustring& n)
{
    vector<ChannelBase*>::iterator i = Session.channels.begin();

    ustring chan = Util::lower(n);
    for (;i != Session.channels.end(); ++i) {
        ustring name = Util::lower((*i)->getName());
        if (name == chan) {
            Session.channels.erase(i);
            return true;
        }
    }
    return false;
}


vector<ChannelBase*> ServerConnection::findUser(const ustring& n)
{
    vector<ChannelBase*> chans;
    vector<ChannelBase*>::iterator i = Session.channels.begin();

    for (;i != Session.channels.end(); ++i) {
        if ((*i)->findUser(n)) {
            chans.push_back(*i);
        }
    }
    return chans;
}

Channel* ServerConnection::findChannel(const ustring& c)
{
    ustring chan = Util::lower(c);
    vector<ChannelBase*>::iterator i = Session.channels.begin();
    for (;i != Session.channels.end(); ++i) {
        ustring name = Util::lower((*i)->getName());
        if (name == chan)
              return dynamic_cast<Channel*>(*i);
    }
    return 0;
}

Query* ServerConnection::findQuery(const ustring& c)
{
    ustring chan = Util::lower(c);
    vector<ChannelBase*>::iterator i = Session.channels.begin();
    for (;i != Session.channels.end(); ++i) {
        ustring name = Util::lower((*i)->getName());
        if (name == chan)
              return dynamic_cast<Query*>(*i);
    }
    return 0;
}

void ServerConnection::sendCmds()
{
    vector<ustring>::iterator i;
    for (i = Session.cmds.begin(); i != Session.cmds.end(); ++i) {
        if (i->empty())
              continue;
        if (i->at(0) == '/') {

            ustring::size_type pos = i->find_first_of(" ");

            ustring params;
            if (pos != ustring::npos) {
                params = i->substr(pos + 1);
            }

            try {

                Commands::send(this, Util::upper(i->substr(1, pos - 1)), params);

            } catch (CommandException& ce) {

                FE::emit(FE::get(CLIENTMSG) << ce.what(), FE::CURRENT, this);
            }
        }
    }
}
