/***************************************************************************
 *   Copyright (C) 2007 by Anistratov Oleg                                 *
 *   ower@users.sourceforge.net                                            *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License version 2        *
 *   as published by the Free Software Foundation;                         *
 *                                                                         *
 *   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.                          *
 *                                                                         *
 ***************************************************************************/

#ifndef CHATCORE_H
#define CHATCORE_H

#include <QObject>
#include <QThread>
#include <QUdpSocket>
#include <QHostInfo>
#include <QByteArray>
#include <QColor>
#include <QSettings>
#include <QTimer>
#include <QHostAddress>
#include <QMap>
#include <QDir>
#include <QTextDocument>

#include "globals.h"
#include "abstractchatcore.h"

class ChatWgt;
class NLAmarok;
class ReceiverThread;
class SenderThread;
class LargeDatagram;
class UserInfo;
class QChatSettings;
class QC_DatagramHeader;
class UserProfile;
class Smile;
class SingleMsgsHistoryModel;
class SingleMessage;
class PluginManager;

struct CmdArgs
{
  int port_in;
  int port_out;
  QString chat_log_dir;
  QString settings_dir;

  bool show_help;
  bool show_version;
  bool show_author;

  QString unknown_option;

  CmdArgs() : port_in(-1), port_out(-1), show_help(false), show_version(false), show_author(false), unknown_option(""){}
};

class TcpReceiverThread;

class MessageFilter;

/**
  * @author Anistratov Oleg <ower@users.sourceforge.net>
  * @brief Class contains itself a core of non gui functionality of QChat
  *
  * Class manages sending and receiving data over network, preprocessing it(or fully processing) and sending to next level to  ChatWgt or appropriate ChannelWgt
  *
*/
class ChatCore : public QThread, public AbstractChatCore
{
  Q_OBJECT
  public:
    enum Mode{Serverless, Server, Combined};

  private:
    /// Shows whether user is logined on server or not
    bool m_loggedIn;

    /// ReceiverThread object using for tcp mode(server mode)
    TcpReceiverThread* m_tcpReceiver;

    /// ReceiverThread object using for usd mode(serverless mode)
    ReceiverThread*    m_udpReceiver;

    QByteArray      m_smilesParam;

    /// Stores main window geometry
    QByteArray      m_geometry;

    /// Stores main window state
    QByteArray      m_state;
    QString         m_lang;
    int             m_needCheckIp;

    QMap<QString, QByteArray> m_channelsStates;

    QString         m_nowListening;
    bool            m_nlIsNew;
#if defined(Q_OS_LINUX)
    NLAmarok*       m_nlAmarok;
#endif
    ChatWgt*        m_chatWgt;
    ReceiverThread* m_receiver;
    SenderThread*   m_sender;

    UserInfo*       m_myInfo;
    QChatSettings*  m_settings;
    QMap<QString, UserProfile*>
                    m_profiles;
    UserProfile*    m_currentProfile;

    QSettings*      m_qsettings;
    QTimer*         m_statusTimer;

    QUdpSocket   m_socketOut;

    // NowListening
    QString m_nlTitle;
    QString m_nlArtist;
    QString m_nlAlbum;

    static QTimer* m_chatTimer;
    quint32 m_chatTime;

    Mode m_mode;
    // user id on server
    int m_uid;

    PluginManager* m_pluginManager;

  public:
    SingleMsgsHistoryModel* m_smhModel;

  private:
    QString homePath(){return QDir::homePath ();}
    MessageFilter* filter(){return QChatSettings::settings()->messageFilter();}

  public:
    ChatCore(QObject *parent = 0);
    ~ChatCore();

    const QChatSettings* const constSettings(){return QChatSettings::settings();}
    QChatSettings* settings(){return QChatSettings::settings();}

    const QByteArray & geometry() const {return m_geometry;}
    const QByteArray & state() const {return m_state;}
    int needCheckIp() const {return m_needCheckIp;}
    QByteArray channelState(const QString & ch) const {return m_channelsStates.value(ch);}

    void setChatWgt(ChatWgt* chw){m_chatWgt = chw;}

    void run(){exec();}
    void stopThreads();

    bool initSettings(QString);

    void slot_loadSettings();
    UserProfile* readProfile(const QString &);
    void writeProfile(const UserProfile*);

    QStringList profiles() const;
    const QString & currentProfile() const;

    /**
     *
     * @param uid unique user id. in serverless mode users IP address serves as uid.
     *
     */
    void prepareDatagramWrapper(AbstractChatCore::DataType dtgrm_type, quint64 uid, const QString & msg  = "",
                                AbstractChatCore::ChannelType  chnnl_type  = Common, quint64 dtgrm_id = 0, quint64 dtgrm_num = 0);

    /// otsylaet dannye nahodyaschiesya v $m_outputBuffer po addressu $addr
    void sendData(quint64);

    void startThreads();
    void processData();

    quint32 initSendingFile  (quint64, const QString &, QObject*);
    void    initReceivingFile(QObject*);

    /**
     * adding all smiles in $message to $m_parametrs in reason that smiles on receiver side
     * are looking such as smiles on sender side
     * @param  message - message to process
     */
    void processSmiles(QString);
    void nextSmile(const QString &);

    ReceiverThread*    receiver()   {return m_receiver;}
    TcpReceiverThread* tcpReceiver(){return m_tcpReceiver;}
    ReceiverThread*    udpReceiver(){return m_udpReceiver;}

    void initMode(Mode, bool = false);

    void saveSettings(bool);

    SingleMsgsHistoryModel* smhModel(){return m_smhModel;}
    void initPlugins();
    PluginManager* pluginManager(){return m_pluginManager;}
    UserInfo* getUserInfo(quint64 uid);

    bool loggedIn();

  public slots:
    void slot_prepareAndSend    (const QString &, quint64, AbstractChatCore::DataType, const QString & = "", AbstractChatCore::ChannelType = Common, QByteArray* = NULL);
    void slot_sendMessage       (const QString &, quint64, AbstractChatCore::ChannelType, QTextDocument*);
    void slot_statusAnswer      (const QString &, quint64, AbstractChatCore::ChannelType ch_type = Common, bool changed = 0, bool = 0);
    void slot_infoAnswer        (const QString &, quint64, AbstractChatCore::ChannelType ch_type = Common, uchar = 0);

    void slot_avatarAnswer      (const QString &, quint64, AbstractChatCore::ChannelType ch_type = Common);

    void slot_privateChatRequest(const QString &, quint64);
    void slot_infStatusChanged  (const QString & );

    void slot_singleMessage     (const QString & msg, quint64, bool/*, quint32 ch_type = 0*/);
    void slot_msgsHistoryAnswer (const QString &, quint64, const QByteArray &, AbstractChatCore::ChannelType);
    void slot_msgsNumAnswer     (const QString &, quint64, quint32, AbstractChatCore::ChannelType);
    void slot_requestFragments  (char*, quint32, quint32, quint64);

    void slot_processData     (char*, quint16);
    void slot_processLargeData(LargeDatagram*);
    void slot_bindInputPort(int);
    void slot_acceptReceiving(quint16, quint64);
    void slot_saveSettings();

    void slot_setNl(const QString &, const QString &, const QString &);

    /**
     *
     * @param  reason reason of rejecting:\n
     * 0 - rejected(receiving didn't ever began)\n
     * 1 - cancelled
     */
    void slot_rejectReceiving(quint16, quint64, int reason);
    void slot_confirmReceivingFile(quint8, quint16, quint64);

    void slot_openSocketError(quint16);

    /**
     * setting the profile with name $name(if it exists) to be current profile
     * @param  name name of profile to load
     */
    void slot_loadProfile  (const QString & name);
    void slot_renameProfile(const QString &, const QString &);
    void slot_deleteProfile(const QString &);
    void slot_updateChatTime(){m_chatTime += 1;}

    void setChannelState(const QString & ch, const QByteArray & ba) {m_channelsStates.insert(ch, ba);}

    void sendPluginData (const QString &, const QMap<QString, QByteArray> &, quint64,
                         AbstractChatCore::DataType, const QString &, uint, const QString &);

    void createReceiverConnections();
    void slot_setMode();
    void slot_login(const QHostAddress&, const QString&);
    void disconnectFromServer();
    void changeLogin(const QString&);
    void changeProtocolVersion(uint);
    void slot_disconnectedFromServer();

  public:
    void setLang(const QString & value){m_lang = value;}
    const QString & lang() const {return m_lang;}

  signals:
    void closed             ();
    void singleMessageIn    (SingleMessage*, bool);
    void privateChatRequest (const QString &, quint64);
    void profileLoaded      (const QString &);
    void chatDataReceived   (QC_DatagramHeader*);
    void wantSendLargeData  (char*, quint16, char* , quint32, quint64, quint32);
    void wantSendFile       (char*, quint16, const QString &, quint64, quint32);
    void wantChangeInputPort(quint16);
    void wantLogin(const QString&, const QHostAddress&, uint, const QString&);
    void loginFinished(int, const QString&);
    void disconnectedFromServer();
};

#endif
