/*
  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.
*/

/*
  Copyright (C) 2008 Eike Hein <hein@kde.org>
*/

#include "connectionmanager.h"
#include "connectionsettings.h"
#include "serversettings.h"
#include "servergroupsettings.h"
#include "konversationapplication.h"
#include "konversationmainwindow.h"
#include "statuspanel.h"

#include <tqregexp.h>

#include <tdelocale.h>
#include <tdemessagebox.h>


ConnectionManager::ConnectionManager(TQObject* parent) : TQObject(parent)
{
    connect(this, TQT_SIGNAL(requestReconnect(Server*)), this, TQT_SLOT(handleReconnect(Server*)));
}

ConnectionManager::~ConnectionManager()
{
}

void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, const TQString& target,
    const TQString& port, const TQString& password, const TQString& nick, const TQString& channel,
    bool useSSL)
{
    ConnectionSettings settings;

    if (target.startsWith("irc://"))
        decodeIrcUrl(target, settings);
    else
    {
        decodeAddress(target, settings);

        Konversation::ServerSettings server = settings.server();

        if (!port.isEmpty()) server.setPort(port.toInt());

        if (!password.isEmpty()) server.setPassword(password);

        if (useSSL) server.setSSLEnabled(true);

        settings.setServer(server);

        if (!nick.isEmpty()) settings.setInitialNick(nick);

        if (!channel.isEmpty())
        {
            Konversation::ChannelSettings channelSettings(channel);

            settings.setInitialChannel(channelSettings);
        }
    }

    connectTo(flag, settings);
}

void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, int serverGroupId)
{
    ConnectionSettings settings;

    Konversation::ServerGroupSettingsPtr serverGroup;

    serverGroup = Preferences::serverGroupById(serverGroupId);

    if (serverGroup)
    {
        settings.setServerGroup(serverGroup);

        if (serverGroup->serverList().size() > 0)
            settings.setServer(serverGroup->serverList()[0]);
    }

    connectTo(flag, settings);
}

void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, ConnectionSettings& settings)
{
    if (!settings.isValid()) return;

    emit closeServerList();

    if (flag != Konversation::CreateNewConnection
        && reuseExistingConnection(settings, (flag == Konversation::PromptToReuseConnection)))
    {
        return;
    }

    IdentityPtr identity = settings.identity();

    if (!identity || !validateIdentity(identity)) return;

    KonversationApplication* konvApp = static_cast<KonversationApplication *>(kapp);
    KonversationMainWindow* mainWindow = konvApp->getMainWindow();

    Server* server = new Server(this, settings);

    enlistConnection(server->connectionId(), server);

    connect(server, TQT_SIGNAL(destroyed(int)), this, TQT_SLOT(delistConnection(int)));

    connect(server, TQT_SIGNAL(connectionStateChanged(Server*, Konversation::ConnectionState)),
            this, TQT_SLOT(handleConnectionStateChange(Server*, Konversation::ConnectionState)));

    connect(server, TQT_SIGNAL(awayState(bool)), this, TQT_SIGNAL(connectionChangedAwayState(bool)));

    connect(server, TQT_SIGNAL(nicksNowOnline(Server*, const TQStringList&, bool)),
        mainWindow, TQT_SLOT(setOnlineList(Server*, const TQStringList&,bool)));
    connect(server, TQT_SIGNAL(awayInsertRememberLine(Server*)),
        mainWindow, TQT_SIGNAL(triggerRememberLines(Server*)));

    connect(mainWindow, TQT_SIGNAL(startNotifyTimer(int)), server, TQT_SLOT(startNotifyTimer(int)));

    connect(server, TQT_SIGNAL(multiServerCommand(const TQString&, const TQString&)),
        konvApp, TQT_SLOT(sendMultiServerCommand(const TQString&, const TQString&)));
}

void ConnectionManager::enlistConnection(int connectionId, Server* server)
{
    m_connectionList.insert(connectionId, server);
}

void ConnectionManager::delistConnection(int connectionId)
{
    m_connectionList.remove(connectionId);
}

void ConnectionManager::handleConnectionStateChange(Server* server, Konversation::ConnectionState state)
{
    emit connectionChangedState(server, state);

    int identityId = server->getIdentity()->id();

    if (state == Konversation::SSConnected)
    {
        if (!m_activeIdentities.contains(identityId))
        {
            m_activeIdentities.append(identityId);

            emit identityOnline(identityId);
        }
    }
    else if (state != Konversation::SSConnecting)
    {
        if (m_activeIdentities.contains(identityId))
        {
            m_activeIdentities.remove(identityId);

            emit identityOffline(identityId);
        }
    }

    if (state == Konversation::SSInvoluntarilyDisconnected)
    {
        // The asynchronous invocation of handleReconnect() makes sure that
        // connectionChangedState() is emitted and delivered before it runs
        // (and causes the next connection state change to occur).
        emit requestReconnect(server);
    }
}

void ConnectionManager::handleReconnect(Server* server)
{
    if (!Preferences::autoReconnect()) return;

    ConnectionSettings settings = server->getConnectionSettings();

    uint reconnectCount = Preferences::reconnectCount();

    // For server groups, one iteration over their server list shall count as one
    // connection attempt.
    if (settings.serverGroup())
        reconnectCount = reconnectCount * settings.serverGroup()->serverList().size();

    if (reconnectCount == 0 || settings.reconnectCount() < reconnectCount)
    {
        if (settings.serverGroup() && settings.serverGroup()->serverList().size() > 1)
        {
            Konversation::ServerList serverList = settings.serverGroup()->serverList();

            int index = serverList.findIndex(settings.server());
            int size = serverList.size();

            if (index == size - 1 || index == -1)
                settings.setServer(serverList[0]);
            else if (index < size - 1)
                settings.setServer(serverList[index+1]);

            server->setConnectionSettings(settings);

            server->getStatusView()->appendServerMessage(i18n("Info"),
                i18n("Trying to connect to %1 in %2 seconds.")
                .arg(settings.server().host())
                .arg(Preferences::reconnectDelay()));
        }
        else
        {
            server->getStatusView()->appendServerMessage(i18n("Info"),
                i18n("Trying to reconnect to %1 in %2 seconds.")
                .arg(settings.server().host())
                .arg(Preferences::reconnectDelay()));
        }

        server->getConnectionSettings().incrementReconnectCount();

        TQTimer::singleShot(Preferences::reconnectDelay() * 1000, server, TQT_SLOT(connectToIRCServer()));
    }
    else
        server->getStatusView()->appendServerMessage(i18n("Error"), i18n("Reconnection attempts exceeded."));
}

void ConnectionManager::quitServers()
{
    TQMap<int, Server*>::ConstIterator it;

    for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
        it.data()->quitServer();
}

void ConnectionManager::decodeIrcUrl(const TQString& url, ConnectionSettings& settings)
{
    if (!url.startsWith("irc://")) return;

    TQString mangledUrl = url;

    mangledUrl.remove(TQRegExp("^irc:/+"));

    if (mangledUrl.isEmpty()) return;

    // Parsing address and channel.
    TQStringList mangledUrlSegments;

    mangledUrlSegments = TQStringList::split('/', mangledUrl, false);

    // Check for ",isserver".
    if (mangledUrlSegments[0].contains(','))
    {
        TQStringList addressSegments;
        bool checkIfServerGroup = true;

        addressSegments = TQStringList::split(',', mangledUrlSegments[0], false);

        if (addressSegments.grep("isserver", false).size() > 0)
            checkIfServerGroup = false;

        decodeAddress(addressSegments[0], settings, checkIfServerGroup);
    }
    else
        decodeAddress(mangledUrlSegments[0], settings);

    TQString channel;
    Konversation::ChannelSettings channelSettings;

    // Grabbing channel from in front of potential ?key=value parameters.
    if (mangledUrlSegments.size() > 1)
        channel = mangledUrlSegments[1].section('?', 0, 0);

    if (!channel.isEmpty())
    {
        // Add default prefix to channel if necessary.
        if (!channel.contains(TQRegExp("^[#+&]{1}")))
            channel = '#' + channel;

        channelSettings.setName(channel);
    }

    // Parsing ?key=value parameters.
    TQString parameterString;

    if (mangledUrlSegments.size() > 1)
        parameterString = mangledUrlSegments[1].section('?', 1);

    if (parameterString.isEmpty() && mangledUrlSegments.size() > 2)
        parameterString = mangledUrlSegments[2];

    if (!parameterString.isEmpty())
    {
        TQRegExp parameterCatcher;

        parameterCatcher.setPattern("pass=([^&]+)");

        if (parameterCatcher.search(parameterString) != -1)
        {
            Konversation::ServerSettings server = settings.server();

            server.setPassword(parameterCatcher.cap(1));

            settings.setServer(server);
        }

        parameterCatcher.setPattern("key=([^&]+)");

        if (parameterCatcher.search(parameterString) != -1)
            channelSettings.setPassword(parameterCatcher.cap(1));
    }

    // Assigning channel.
    if (!channelSettings.name().isEmpty())
        settings.setInitialChannel(channelSettings);
}

void ConnectionManager::decodeAddress(const TQString& address, ConnectionSettings& settings,
                                      bool checkIfServerGroup)
{
    TQString host;
    TQString port = "6667";

    // Full-length IPv6 address with port
    // Example: RFC 2732 notation:     [2001:0DB8:0000:0000:0000:0000:1428:57ab]:6666
    // Example: Non-RFC 2732 notation: 2001:0DB8:0000:0000:0000:0000:1428:57ab:6666
    if (address.contains(':')==8)
    {
        host = address.section(':',0,-2).remove("[").remove("]");
        port = address.section(':',-1);
    }
    // Full-length IPv6 address without port or not-full-length IPv6 address with port
    // Example: Without port, RFC 2732 notation:     [2001:0DB8:0000:0000:0000:0000:1428:57ab]
    // Example: Without port, Non-RFC 2732 notation: 2001:0DB8:0000:0000:0000:0000:1428:57ab
    // Example: With port, RFC 2732 notation:        [2001:0DB8::1428:57ab]:6666
    else if (address.contains(':')>=4)
    {
        // Last segment does not end with ], but the next to last does;
        // Assume not-full-length IPv6 address with port
        // Example: [2001:0DB8::1428:57ab]:6666
        if (address.section(':',0,-2).endsWith("]") && !address.section(':',-1).endsWith("]"))
        {
            host = address.section(':',0,-2).remove("[").remove("]");
            port = address.section(':',-1);
        }
        else
        {
            TQString addressCopy = address;
            host = addressCopy.remove("[").remove("]");
        }
    }
    // IPv4 address or ordinary hostname with port
    // Example: IPv4 address with port: 123.123.123.123:6666
    // Example: Hostname with port:     irc.bla.org:6666
    else if (address.contains(':')==1)
    {
        host = address.section(':',0,-2);
        port = address.section(':',-1);
    }
    else
        host = address;

    // Try to assign server group.
    if (checkIfServerGroup && Preferences::isServerGroup(host))
    {
        // If host is found to be the name of a server group.

        int serverGroupId = Preferences::serverGroupIdByName(host);

        Konversation::ServerGroupSettingsPtr serverGroup;

        serverGroup = Preferences::serverGroupById(serverGroupId);

        settings.setServerGroup(serverGroup);

        if (serverGroup->serverList().size() > 0)
            settings.setServer(serverGroup->serverList()[0]);
    }
    else
    {
        if (Preferences::serverGroupByServer(host))
        {
            // If the host is found to be part of a server group's server list.

            Konversation::ServerGroupSettingsPtr serverGroup;

            serverGroup = Preferences::serverGroupByServer(host);

            settings.setServerGroup(serverGroup);
        }

        Konversation::ServerSettings server;

        server.setHost(host);
        server.setPort(port.toInt());

        settings.setServer(server);
    }
}

bool ConnectionManager::reuseExistingConnection(ConnectionSettings& settings, bool interactive)
{
    Server* dupe = 0;
    ConnectionDupe dupeType;
    bool doReuse = true;

    KonversationApplication* konvApp = static_cast<KonversationApplication *>(kapp);
    KonversationMainWindow* mainWindow = konvApp->getMainWindow();

    TQMap<int, Server*>::ConstIterator it;

    for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
    {
        if (it.data()->getServerGroup() && settings.serverGroup()
            && it.data()->getServerGroup() == settings.serverGroup())
        {
            dupe = it.data();
            dupeType = SameServerGroup;

            break;
        }
    }

    if (!dupe)
    {
        for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
        {
            if (it.data()->getConnectionSettings().server() == settings.server())
            {
                dupe = it.data();
                dupeType = SameServer;

                break;
            }
        }
    }

    if (dupe && interactive)
    {
        int result = KMessageBox::warningContinueCancel(
            mainWindow,
            i18n("You are already connected to %1. Do you want to open another connection?")
                .arg(dupe->getDisplayName()),
            i18n("Already connected to %1").arg(dupe->getDisplayName()),
            i18n("Create connection"),
            "ReuseExistingConnection");

        if (result == KMessageBox::Continue) doReuse = false;
    }

    if (dupe && doReuse)
    {
        if (interactive && dupeType == SameServerGroup
            && !(dupe->getConnectionSettings().server() == settings.server()))
        {
            int result = KMessageBox::warningContinueCancel(
                mainWindow,
                i18n("You are presently connected to %1 via '%2' (port %3). Do you want to switch to '%4' (port %5) instead?")
                    .arg(dupe->getDisplayName())
                    .arg(dupe->getServerName())
                    .arg(dupe->getPort())
                    .arg(settings.server().host())
                    .arg(settings.server().port()),
                i18n("Already connected to %1").arg(dupe->getDisplayName()),
                i18n("Switch Server"),
                "ReconnectWithDifferentServer");

            if (result == KMessageBox::Continue)
            {
                dupe->disconnect();

                dupe->setConnectionSettings(settings);
            }
        }

        if (!dupe->isConnected())
        {
            if (!settings.initialChannel().name().isEmpty())
                dupe->updateAutoJoin(settings.initialChannel());

            if (!dupe->isConnecting())
                dupe->reconnect();
        }
        else
        {
            if (!settings.initialChannel().name().isEmpty())
            {
                dupe->sendJoinCommand(settings.initialChannel().name(),
                                      settings.initialChannel().password());
            }
        }
    }

    return (dupe && doReuse);
}

bool ConnectionManager::validateIdentity(IdentityPtr identity, bool interactive)
{
    KonversationApplication* konvApp = static_cast<KonversationApplication *>(kapp);
    KonversationMainWindow* mainWindow = konvApp->getMainWindow();

    TQString errors;

    if (identity->getIdent().isEmpty())
        errors+=i18n("Please fill in your <b>Ident</b>.<br>");

    if (identity->getRealName().isEmpty())
        errors+=i18n("Please fill in your <b>Real name</b>.<br>");

    if (identity->getNickname(0).isEmpty())
        errors+=i18n("Please provide at least one <b>Nickname</b>.<br>");

    if (!errors.isEmpty())
    {
        if (interactive)
        {
            int result = KMessageBox::warningContinueCancel(mainWindow,
                            i18n("<qt>Your identity \"%1\" is not set up correctly:<br>%2</qt>")
                                .arg(identity->getName()).arg(errors),
                            i18n("Identity Settings"),
                            i18n("Edit Identity..."));

            if (result == KMessageBox::Continue)
            {
                identity = mainWindow->editIdentity(identity);

                if (identity && validateIdentity(identity, false))
                    return true;
                else
                    return false;
            }
            else
                return false;
        }

        return false;
    }

    return true;
}

TQPtrList<Server> ConnectionManager::getServerList()
{
    TQPtrList<Server> serverList;

    TQMap<int, Server*>::ConstIterator it;

    for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
        serverList.append(it.data());

    return serverList;
}

Server* ConnectionManager::getServerByConnectionId(int connectionId)
{
    if (m_connectionList.contains(connectionId))
        return m_connectionList[connectionId];
    else
        return 0;
}

Server* ConnectionManager::getServerByName(const TQString& name)
{
    TQMap<int, Server*>::ConstIterator it;

    for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
    {
        if (it.data()->getDisplayName() == name || it.data()->getServerName() == name)
            return it.data();
    }

    return 0;
}

Server* ConnectionManager::getAnyServer()
{
    if ( m_connectionList.count() > 0)
        return m_connectionList[0];

    return 0;
}

#include "connectionmanager.moc"
