/*
 * psiaccount.h - handles a Psi account
 * Copyright (C) 2001-2005  Justin Karneges
 *
 * 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.
 *
 * You can also redistribute and/or modify this program under the
 * terms of the Psi License, specified in the accompanied COPYING
 * file, as published by the Psi Project; either dated January 1st,
 * 2005, 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, see <https://www.gnu.org/licenses/>.
 *
 */

#ifndef PSIACCOUNT_H
#define PSIACCOUNT_H

#include "activity.h"
#include "filesharingdownloader.h"
#include "geolocation.h"
#include "mood.h"
#include "psiactions.h"
#include "psievent.h"
#include "xmpp_encryptionhandler.h"
#include "xmpp_reference.h"
#include "xmpp_rosterx.h"
#include "xmpp_status.h"

#include <QList>
#include <QUrl>
#include <functional>

class AvatarFactory;
class AvCallManager;
class BookmarkManager;
class ChatDlg;
class ConferenceBookmark;
class ContactProfile;
class EDB;
class EventDlg;
class EventQueue;
class FileSharingDeviceOpener;
class FileSharingItem;
#ifdef GOOGLE_FT
class GoogleFileTransfer;
#endif
class PEPManager;
class PrivacyManager;
class PsiAccount;
class PsiCon;
class PsiContact;
class PsiContactList;
class PsiHttpAuthRequest;
class PsiIcon;
class QHostAddress;
class QIcon;
class QMimeData;
class QSSLCert;
class QString;
class QWidget;
class TabManager;
class Tune;
class URLBookmark;
class UserAccount;
class UserList;
class UserListItem;
class UserResource;
class VarList;
class VoiceCaller;
#ifdef WHITEBOARDING
class WbManager;
#endif

namespace XMPP {
class AdvancedConnector;
class Client;
class Jid;
class Message;
class PubSubItem;
class PubSubRetraction;
class QCATLSHandler;
class Resource;
class RosterItem;
class ServerInfoManager;
class Stream;
// class StreamError;
class XData;
};
using namespace XMPP;

// sick sick remove this someday please!
struct GCContact;

class PsiAccount : public QObject, EncryptionHandler {
    Q_OBJECT
protected:
    PsiAccount(const UserAccount &acc, PsiContactList *parent, TabManager *tabManager);
    virtual void init();

public:
    static PsiAccount *create(const UserAccount &acc, PsiContactList *parent, TabManager *tabManager);
    virtual ~PsiAccount();

    bool enabled() const;
    void setEnabled(bool e = true);

    bool           isAvailable() const;
    bool           isActive() const;
    bool           isConnected() const;
    const QString &name() const;
    const QString &id() const;

    bool  alerting() const;
    QIcon alertPicture() const;

    const UserAccount &userAccount() const;
    UserAccount        accountOptions() const;
    virtual void       setUserAccount(const UserAccount &);
    const Jid         &jid() const;
    QString            nameWithJid() const;

    void                    updateFeatures();
    XMPP::Client           *client() const;
    virtual ContactProfile *contactProfile() const;
    EventQueue             *eventQueue() const;
    EDB                    *edb() const;
    PsiCon                 *psi() const;
    AvatarFactory          *avatarFactory() const;
    PrivacyManager         *privacyManager() const;
    VoiceCaller            *voiceCaller() const;
#ifdef WHITEBOARDING
    WbManager *wbManager() const;
#endif
    Status        status() const;
    static Status loggedOutStatus();
    int           defaultPriority(const XMPP::Status &);
    void          setStatusDirect(const XMPP::Status &, bool withPriority = false);
    void          setStatusActual(const XMPP::Status &);

    enum AutoAway { AutoAway_None = 0, AutoAway_Away, AutoAway_XA, AutoAway_Offline };

    void setAutoAwayStatus(AutoAway status);

    bool               noPopup() const;
    bool               loggedIn() const;
    void               setNick(const QString &);
    QString            nick() const;
    const Mood        &mood() const;
    const Activity    &activity() const;
    const GeoLocation &geolocation() const;
    bool               hasPgp() const;
    QString            pgpKeyId() const;
    QHostAddress      *localAddress() const;
    void               setKnownPgpKeys(const VarList &list);
    void               removeKnownPgpKey(const QString &jid);

    ChatDlg         *findChatDialog(const Jid &jid, bool compareResource = true) const;
    ChatDlg         *findChatDialogEx(const Jid &jid, bool ignoreResource = false) const;
    QList<ChatDlg *> findChatDialogs(const Jid &jid, bool compareResource = true) const;

    QList<PsiContact *> activeContacts() const;

    template <typename T> inline T findDialog(const Jid &jid = Jid(), bool compareResource = true) const
    {
        return static_cast<T>(findDialog(std::remove_pointer<T>::type::staticMetaObject, jid, compareResource));
    }
    template <typename T> inline QList<T> findDialogs(const Jid &jid = Jid(), bool compareResource = true) const
    {
        QList<T> list;
        findDialogs(std::remove_pointer<T>::type::staticMetaObject, jid, compareResource,
                    reinterpret_cast<QList<void *> *>(&list));
        return list;
    }
    template <typename T> inline QList<T> findAllDialogs() const
    {
        QList<T> list;
        findDialogs(std::remove_pointer<T>::type::staticMetaObject, reinterpret_cast<QList<void *> *>(&list));
        return list;
    }

    void dialogRegister(QWidget *w, const Jid &jid = Jid());
    void dialogUnregister(QWidget *w);

    bool notifyOnline() const;

    void updateAlwaysVisibleContact(PsiContact *pc);

    void modify();
    void reconfigureFTManager();
    void changeVCard();
    void changePW();
    void changeStatus(int, bool forceDialog = false);
    void doDisco();
    void doWakeup();

    void        showXmlConsole();
    void        openAddUserDlg();
    void        openAddUserDlg(const XMPP::Jid &jid, const QString &nick, const QString &group);
    bool        groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &pass,
                              bool nohistory = false);
    void        groupChatSetStatus(const QString &host, const QString &room, const Status &);
    void        groupChatChangeNick(const QString &host, const QString &room, const QString &nick, const Status &);
    void        groupChatLeave(const QString &host, const QString &room);
    void        setLocalMucBookmarks(const QStringList &sl);
    void        setIgnoreMucBookmarks(const QStringList &sl);
    QStringList localMucBookmarks() const;
    QStringList ignoreMucBookmarks() const;

    void shareImage(const Jid &target, const QImage &image, const QString &description,
                    std::function<void(const QString &getUrl)> callback = std::function<void(const QString &getUrl)>());

    Jid                        realJid(const Jid &j) const;
    PsiContact                *selfContact() const;
    const QList<PsiContact *> &contactList() const;
    int                        onlineContactsCount() const;
    PsiContact                *findContact(const Jid &jid) const;
    UserListItem              *find(const Jid &) const;
    QList<UserListItem *>      findRelevant(const Jid &) const;
    UserListItem              *findFirstRelevant(const Jid &) const;
    UserList                  *userList() const;
    bool                       usingSSL() const;

    bool checkConnected(QWidget *parent = nullptr);

    enum SoundType {
        eNone = -1,

        eMessage = 0,
        eChat1,
        eChat2,
        eGroupChat,
        eHeadline,
        eSystem,
        eOnline,
        eOffline,
        eSend,
        eIncomingFT,
        eFTComplete,
        eSoundLast
    };
    void playSound(SoundType onevent);

#ifdef PSI_PLUGINS
    void createNewPluginEvent(int account, const QString &jid, const QString &descr, QObject *receiver,
                              const char *slot);
    void createNewMessageEvent(const QDomElement &element);
#endif

    QStringList hiddenChats(const Jid &) const;

    bool isPgpEnabled(const Jid &jid) const;
    void setPgpEnabled(const Jid &jid, const bool value);
    int  sendPgpEncryptedMessage(const Message &);

    // sucks sucks sucks sucks sucks sucks sucks
    GCContact         *findGCContact(const Jid &j) const;
    XMPP::Status       gcContactStatus(const Jid &j);
    const QStringList &groupchats() const;

    void toggleSecurity(const Jid &, bool);
    bool ensureKey(const Jid &);
    void tryVerify(UserListItem *, UserResource *);

    static void getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QCATLSHandler *tlsHandler, QString *_str,
                             bool *_reconn, bool *_badPass, bool *_disableAutoConnect, bool *_isTemporaryAuthFailure,
                             bool *_needAlert);

    void deleteQueueFile();

    PEPManager        *pepManager();
    ServerInfoManager *serverInfoManager();
    BookmarkManager   *bookmarkManager();
    AvCallManager     *avCallManager();

    void    clearCurrentConnectionError();
    QString currentConnectionError() const;
    int     currentConnectionErrorCondition() const;

    enum xmlRingType { RingXmlIn, RingXmlOut, RingSysMsg };
    class xmlRingElem {
    public:
        int       type;
        QDateTime time;
        QString   xml;
    };
    QList<xmlRingElem> dumpRingbuf();
    void               clearRingbuf();
    void               addMucItem(const Jid &);
    QStringList        groupList() const;
    void               updateEntry(const UserListItem &u);

    void resetLastManualStatusSafeGuard();
    void savePassword();
    void loadBob(const Jid &jid, const QString &cid, QObject *context,
                 std::function<void(bool, const QByteArray &, const QByteArray &)> callback);

    FileSharingDeviceOpener *fileSharingDeviceOpener() const;
    void                     resolveContactName(const Jid &j);

    void setClientVersionInfoMap(const QVariantMap &info);

signals:
    void accountDestroyed();
    void connectionError(const QString &errorInfo);
    void disconnected();
    void reconnecting();
    void updatedActivity();
    void updatedAccount();
    void queueChanged();
    void updateContact(const UserListItem &);
    void updateContact(const Jid &);
    void updateContact(const Jid &, bool);
    void removeContact(const Jid &);
    void nickChanged();
    void pgpKeyChanged();
    void encryptedMessageSent(int, bool, int, const QString &);
    void enabledChanged();
    void startBounce();
    void addedContact(PsiContact *);
    void removedContact(PsiContact *);

    void beginBulkContactUpdate();
    void endBulkContactUpdate();
    void rosterRequestFinished();

public slots:
    void sendFiles(const Jid &, const QStringList &fileList = QStringList());
    void fastLogout();
    void setStatus(const XMPP::Status &, bool withPriority = false, bool isManualStatus = false);
    void showStatusDialog(const QString &presetName);

    void capsChanged(const Jid &);
    void tuneStopped();
    void tunePlaying(const Tune &);

    void incomingVoiceCall(const Jid &);

    void openNextEvent(ActivationType activationType);
    int  forwardPendingEvents(const Jid &jid);
    void autoLogin();
    void reconnectOnce();

    void showCert();

    void openUri(const QUrl &uri);

    // dj_ originally referred to 'direct jabber', if you care
    void dj_sendMessage(Message &, bool log = true);
    void dj_newMessage(const Jid &jid, const QString &body, const QString &subject, const QString &thread);
    void dj_replyMessage(const Jid &jid, const QString &body);
    void dj_replyMessage(const XMPP::Jid &jid, const QString &body, const QString &subject, const QString &thread);
    void dj_addAuth(const Jid &);
    void dj_addAuth(const Jid &, const QString &);
    void dj_add(const XMPP::Jid &, const QString &, const QStringList &, bool authReq);
    void dj_authReq(const Jid &);
    void dj_auth(const Jid &);
    void dj_deny(const Jid &);
    void dj_rename(const Jid &, const QString &);
    void dj_remove(const Jid &);
    void dj_confirmHttpAuth(const PsiHttpAuthRequest &);
    void dj_denyHttpAuth(const PsiHttpAuthRequest &);
    void dj_rosterExchange(const RosterExchangeItems &);
    void dj_formSubmit(const XData &, const QString &, const Jid &);
    void dj_formCancel(const XData &, const QString &, const Jid &);

    void     actionDefault(const Jid &);
    void     actionRecvEvent(const Jid &);
    void     actionRecvRosterExchange(const Jid &, const RosterExchangeItems &);
    void     actionSendMessage(const Jid &);
    void     actionSendMessage(const QList<XMPP::Jid> &);
    void     actionSendUrl(const Jid &);
    void     actionRemove(const Jid &);
    void     actionRename(const Jid &, const QString &);
    void     actionGroupRename(const QString &, const QString &);
    void     actionHistory(const Jid &);
    ChatDlg *actionOpenChat(const Jid &);
    void     actionOpenSavedChat(const Jid &);
    void     actionOpenChat2(const Jid &);
    void     actionOpenChatSpecific(const Jid &);
    void     actionOpenPassiveChatSpecific(const Jid &);
#ifdef WHITEBOARDING
    void actionOpenWhiteboard(const Jid &);
    void actionOpenWhiteboardSpecific(const Jid &, Jid = Jid(), bool = false);
#endif
    void actionAgentSetStatus(const Jid &, const Status &s);
    void actionInfo(const Jid &, bool showStatusInfo = true);
    void actionAuth(const Jid &);
    void actionAuthRequest(const Jid &);
    void actionAuthRemove(const Jid &);
    void actionAdd(const Jid &);
    void actionGroupAdd(const Jid &, const QString &);
    void actionGroupRemove(const Jid &, const QString &);
    void actionGroupsSet(const Jid &, const QStringList &);
    void actionHistoryBox(const PsiEvent::Ptr &e);
    void actionRegister(const Jid &);
    void actionUnregister(const Jid &);
    void actionSearch(const Jid &);
    void actionManageBookmarks();
    void actionJoin(const Jid &mucJid, const QString &password = QString());
    void actionJoin(const ConferenceBookmark &bookmark, bool connectImmediately, bool custom = true);
    void actionDisco(const Jid &, const QString &);
    void actionInvite(const Jid &, const QString &);
    void actionVoice(const Jid &);
    void actionExecuteCommand(const Jid &j, const QString & = QString());
    void actionExecuteCommandSpecific(const Jid &, const QString & = QString());
    void actionSetBlock(bool);
    void actionSetMood();
    void actionSetActivity();
    void actionSetGeoLocation();
    void actionSetAvatar();
    void actionUnsetAvatar();
    void actionQueryVersion(const Jid &j);
    void featureActivated(QString feature, Jid jid, QString node);
    void actionSendStatus(const Jid &jid);

    void actionAssignPgpKey(const Jid &);
    void actionUnassignPgpKey(const Jid &);

    void invokeGCMessage(const Jid &);
    void invokeGCChat(const Jid &);
    void invokeGCInfo(const Jid &);

private slots:
    void tls_handshaken();
    void cs_connected();
    void cs_securityLayerActivated(int);
    void cs_needAuthParams(bool, bool, bool);
    void cs_authenticated();
    void cs_connectionClosed();
    void cs_delayedCloseFinished();
    void cs_warning(int);
    void cs_error(int);
    void client_rosterRequestFinished(bool, int, const QString &);
    void jt_resolveContactName();
    void client_rosterItemAdded(const RosterItem &);
    void client_rosterItemUpdated(const RosterItem &);
    void client_rosterItemRemoved(const RosterItem &);
    void client_resourceAvailable(const Jid &, const Resource &);
    void client_resourceUnavailable(const Jid &, const Resource &);
    void client_presenceError(const Jid &, int, const QString &);
    void client_messageReceived(const Message &);
    void client_subscription(const Jid &, const QString &, const QString &);
    void client_debugText(const QString &);
    void client_groupChatJoined(const Jid &);
    void client_groupChatLeft(const Jid &);
    void client_groupChatPresence(const Jid &, const Status &);
    void client_groupChatError(const Jid &, int, const QString &);
#ifdef GOOGLE_FT
    void incomingGoogleFileTransfer(GoogleFileTransfer *ft);
#endif
    void client_incomingFileTransfer();
    void client_incomingJingle(Jingle::Session *session);
    void sessionStart_finished();

    void serverFeaturesChanged();
    void setPEPAvailable(bool);

    void bookmarksAvailabilityChanged();

    void incomingHttpAuthRequest(const PsiHttpAuthRequest &);

    void reconnect();
    void enableNotifyOnline();

#ifdef WHITEBOARDING
    void wbRequest(const Jid &j, int id);
#endif

    void itemPublished(const Jid &, const QString &, const PubSubItem &);
    void itemRetracted(const Jid &, const QString &, const PubSubRetraction &);

    void chatMessagesRead(const Jid &);
#ifdef GROUPCHAT
    void groupChatMessagesRead(const Jid &);
#endif

    void pgp_verifyFinished();
    void pgp_encryptFinished();
    void pgp_decryptFinished();

    void ed_addAuth(const Jid &);
    void ed_deny(const Jid &);

    void optionsUpdate();

    void processReadNext(const UserListItem &);
    void processReadNext(const Jid &);
    void queryVersionFinished();

protected:
    bool    validRosterExchangeItem(const RosterExchangeItem &);
    QString localHostName();

    void publishTune(const Tune &);
    void setRCEnabled(bool);
    void sessionStarted();

    void stateChanged();
    void profileUpdateEntry(const UserListItem &u);
    void profileRemoveEntry(const Jid &jid);
    void profileAnimateNick(const Jid &jid);
    void profileSetAlert(const Jid &jid, const PsiIcon *icon);
    void profileClearAlert(const Jid &jid);

private slots:
    void eventFromXml(const PsiEvent::Ptr &e);
    void simulateContactOffline(const XMPP::Jid &contact);

private:
    class Private;
    friend class Private;
    Private *d;

    void handleEvent(const PsiEvent::Ptr &e, ActivationType activationType);

    void login();
    void logout(bool fast, const Status &s);

    void          deleteAllDialogs();
    void          simulateContactOffline(UserListItem *);
    void          simulateRosterOffline();
    void          cpUpdate(const UserListItem &, const QString &rname = "", bool fromPresence = false);
    UserListItem *addUserListItem(const Jid &jid, const QString &nick = "");
    void          logEvent(const Jid &, const PsiEvent::Ptr &, int);
    void          queueEvent(const PsiEvent::Ptr &e, ActivationType activationType);
    void          openNextEvent(const UserListItem &, ActivationType activationType);
    void          updateReadNext(const Jid &);
    ChatDlg      *ensureChatDlg(const Jid &);
    void          lastStepLogin();
    void          processIncomingMessage(const Message &);
    void          processMessageQueue();
    void          processPgpEncryptedMessage(const Message &);
    void          processPgpEncryptedMessageNext();
    void          processPgpEncryptedMessageDone();
    void          verifyStatus(const Jid &j, const Status &s);
    bool          passwordPrompt();
    void          sentInitialPresence();
    void          requestAvatarsForAllContacts();

    void      processChatsHelper(const Jid &jid, bool removeEvents);
    void      processChats(const Jid &);
    ChatDlg  *openChat(const Jid &, ActivationType activationType);
    EventDlg *createEventDlg(const Jid &);
    friend class PsiCon;

    bool isDisconnecting, notifyOnlineOk, doReconnect, rosterDone, presenceSent, v_isActive;
    void cleanupStream();

    QWidget *findDialog(const QMetaObject &mo, const Jid &jid, bool compareResource) const;
    void     findDialogs(const QMetaObject &mo, const Jid &jid, bool compareResource, QList<void *> *list) const;
    void     findDialogs(const QMetaObject &mo, QList<void *> *list) const;

    bool decryptMessageElement(QDomElement &element) override;
    bool encryptMessageElement(QDomElement &element) override;
    void userListItemUnavailable(UserListItem *u, const Jid &j, const Resource &r, bool *doSound = nullptr,
                                 bool *doPopup = nullptr);
    void updateClientVersionInfo();
};

#endif // PSIACCOUNT_H
