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

#include "chatcore.h"

#include <assert.h>
#include <string.h>
#include <time.h>

#include <QBuffer>
#include <QFile>
#include <QDateTime>
#include <QTextBlock>
#include <QCryptographicHash>

#include "chatwgt.h"
#include "channelwgt.h"
#include "userinfo.h"
#include "userwgt.h"
#include "qchatsettings.h"
#include "receiverthread.h"
#include "senderthread.h"
#include "largedatagram.h"
#include "edituserinfodlg.h"
#include "nlamarok.h"
#include "userprofile.h"

#include "smileswgt.h"
#include "chattextwgt.h"

#include "userstatistics.h"

#include "singlemessage.h"
#include "singlemsgshistory.h"

#include "pluginsinterfaces.h"
#include "pluginmanager.h"

#include "tcpreceiverthread.h"
#include "messagefilter.h"

QTimer* ChatCore::m_chatTimer = new QTimer;

ChatCore::ChatCore(QObject* parent)
 : QThread(parent),
  AbstractChatCore(),
  m_loggedIn        (false),
  m_needCheckIp     (0),
  m_nowListening    (""),
  m_nlIsNew         (false),
  m_chatWgt         (NULL),
  m_myInfo          (0),
  m_currentProfile  (0),
  m_chatTime        (0),
  m_mode            (Serverless),
  m_uid             (0)
{
  m_sender      = new SenderThread  ();
  m_udpReceiver = new ReceiverThread();
  m_tcpReceiver = new TcpReceiverThread();

  m_udpReceiver->setObjectName("udpReceiver");
  m_tcpReceiver->setObjectName("tcpReceiver");

  m_receiver = m_udpReceiver;

  m_settings = new QChatSettings;

  m_smhModel = new SingleMsgsHistoryModel;

#if defined(Q_OS_LINUX)
  m_nlAmarok = new NLAmarok(this);
#endif

  UserInfo::setMyInfo(m_myInfo);
  QChatSettings::setSettings(m_settings);

  m_pluginManager = new PluginManager;

  m_statusTimer = new QTimer(this);

  m_chatTimer->start(1000);

  connect(m_chatTimer  , SIGNAL(timeout()), this, SLOT(slot_updateChatTime()));
  connect(m_tcpReceiver, SIGNAL(disconnected()), this, SLOT(slot_disconnectedFromServer()));

#if defined(Q_OS_LINUX)
  connect(m_nlAmarok, SIGNAL(updated   (const QString&, const QString&, const QString&)),
          this      , SLOT  (slot_setNl(const QString&, const QString&, const QString&)));
#endif
}

ChatCore::~ChatCore()
{
  qDebug("[~ChatCore]");

  stopThreads();
  delete m_settings;
  delete m_smhModel;
  delete m_pluginManager;

  m_sender     ->deleteLater();
  m_udpReceiver->deleteLater();
  m_tcpReceiver->deleteLater();

  qDebug("[~ChatCore]: end");
}
//\*****************************************************************************
void ChatCore::sendData(quint64 uid)
{
//   qDebug("sending to %u %s", uid, QHostAddress(uid).toString().toAscii().data());

  if(largeDtgrm())
  {
    quint32 id = m_sender->getValidID();

    if(id)
    {
      emit wantSendLargeData(header(), headerSize(), data(), dataSize(), uid, id);
      setNULLdataAndHeader();
    }
  }
  else
  {
    //*********************
    // Compression
    setCompressed(outputBuffer(), settings()->boolOption("UseCompression"));

    if(settings()->boolOption("UseCompression") && protocolVersion() >= 4)
    {
      char* buf;
      uint  size = outputBufferSize();

      buf = compress(outputBuffer(), size);

      if(buf && size < (uint)outputBufferSize())
        setOutputBuffer(buf, size);
      else
        setCompressed(outputBuffer(), false);
      free(buf);
    }
    // Compression
    //*********************

    quint16 size = outputBufferSize() <= MAX_PACKET_LEN ? outputBufferSize() : (MAX_PACKET_LEN);

    if(m_settings->intOption("ProtocolVersion") < 4 && m_receiver == m_udpReceiver)
    {
    }
    else
    {
      setPacketSize(size);
    }

    int bs = -2;

    if(m_mode == Serverless && QChatSettings::settings()->boolOption("UseIPList") && (QHostAddress(uid) == QChatSettings::broadcast()))
    {
      foreach(QHostAddress addr, QChatSettings::ipList())
        m_sender->send(outputBuffer(), size, addr);

      m_sender->send(outputBuffer(), size, QHostAddress(QChatSettings::settings()->hostAddressOption("IP")));
    }
    else
      bs = m_sender->send(outputBuffer(), size, QHostAddress(uid));

    qDebug("[ChatCore::sendData]: dtgrm size = %d, sent = %d\n", size, bs);
  }

  clearParametrs();
}
//\*****************************************************************************
void ChatCore::slot_prepareAndSend(const QString & ch_name_id, quint64 uid, AbstractChatCore::DataType data_type, const QString & msg, AbstractChatCore::ChannelType ch_type, QByteArray* add_pars)
{
  qDebug("[ChatCore::slot_prepareAndSend]: type = %u", data_type);
  qDebug("[ChatCore::slot_prepareAndSend]: 'channel_name' = '%s'", ch_name_id.toLocal8Bit().data());
  qDebug("[ChatCore::slot_prepareAndSend]: 'msg' = '%s'", msg.toLocal8Bit().data());

  if(data_type != AbstractChatCore::SINGLE_MESSAGE)
    addParametr("Channel", ch_name_id.toUtf8());

  if(add_pars)
  {
    parametrs().append(*add_pars);
    add_pars->clear();
  }

  if(data_type == AbstractChatCore::MESSAGE)
  {
    addParametr("HTML", QByteArray());
    addParametr("Color", QByteArray().append(m_settings->myColor().red  ())
                                     .append(m_settings->myColor().green())
                                     .append(m_settings->myColor().blue ()));
    processSmiles(msg);

    bool send_nl = m_nlIsNew && !m_nowListening.isEmpty() && (m_settings->nlMode() & 1);

    if(send_nl)
    {
      m_nowListening = m_settings->strOption("NLFormat");
      m_nowListening.replace("%a", m_nlArtist).replace("%b", m_nlAlbum).replace("%t", m_nlTitle);
    }

    prepareDatagramWrapper(AbstractChatCore::MESSAGE,
                    uid,
                    send_nl ? msg + "\n" + m_nowListening : msg,
                    ch_type);

    m_nlIsNew = (m_nlIsNew && !send_nl);
  }
  else
    prepareDatagramWrapper(data_type, uid, msg, ch_type);

  sendData(uid);
}
//\*****************************************************************************
void ChatCore::slot_singleMessage(const QString & msg, quint64 uid, bool important/*, quint32 type*/)
{
  qDebug("[ChatCore::slot_singleMessage]: addr = '%s'", QHostAddress(uid).toString().toLocal8Bit().data());
  qDebug("[ChatCore::slot_singleMessage]: msg  = '%s'", msg.toLocal8Bit().data());

  if(important)
    addParametr("Important", QByteArray());

  prepareDatagramWrapper(AbstractChatCore::SINGLE_MESSAGE,
                  uid,
                  msg,
                  Common);

  {
    // FIXME this is not optimal solution :)
    //***************
    setInputBuffer(outputBuffer(), outputBufferSize());
    fillHeader();

    UserInfo* info = getUserInfo(uid);

    hdr()->src_ip         = hdr()->dest_ip;

    if(info)
    {
      hdr()->name           = info->nickname();
      hdr()->comp_name      = info->compName();
      hdr()->programVersion = info->programVerID();
    }
    else
      hdr()->name           = "*";
      hdr()->comp_name      = "*";
      hdr()->programVersion = Globals::VersionID;

    //***************

    SingleMessage* msg = new SingleMessage(hdr(), info ? info->ip() : uid);
    m_smhModel->addMessage(msg);
  }

  sendData(uid);
}
//\*****************************************************************************
void ChatCore::slot_statusAnswer(const QString & name_id, quint64 uid, ChannelType channel_type, bool changed, bool send_icon)
{
  if(m_myInfo->status(name_id) == Globals::INVISIBLE)
    return;

  if(send_icon)
  {
    QByteArray data;
    QBuffer    buffer;
    const QImage* icon = m_myInfo->newIconImg();

    buffer.setBuffer(&data);

    if(icon)
    {
      icon->save(&buffer, "PNG", 100);
      delete icon;
    }

    addParametr("Icon"   , data);
  }

  addParametr("Status"   , QByteArray().append((char)m_myInfo->status(name_id)));
  addParametr("Channel"  , name_id.toUtf8());
  addParametr("Version"  , Globals::VersionStr.toUtf8());
  addParametr("StatusDescription", UserInfo::myInfo()->statusDescription(name_id).toUtf8());
  addParametr("Gender"   , QString().setNum(m_myInfo->gender()).toUtf8());
  addParametr("OS"       , UserStatistics::OsString().toUtf8());
  addParametr("Uptime"   , QString().setNum(UserStatistics::getUptime()).toUtf8());
  addParametr("ChatTime" , QString().setNum(m_chatTime).toUtf8());
  addParametr("FirstName", m_myInfo->firstName().toUtf8());
  addParametr("IpAddress", QString().setNum(m_settings->hostAddressOption("IP").toIPv4Address()).toUtf8());

  if(!changed)
    prepareDatagramWrapper(AbstractChatCore::STATUS_ANSWER,
                           uid,
                           QString(""),
                           channel_type);
  else
  {
    QString str = "Status Changed : " + Globals::StatusStr[m_myInfo->status(name_id)];

    if(!UserInfo::myInfo()->statusDescription(name_id).isEmpty())
      str += " (" + UserInfo::myInfo()->statusDescription(name_id) + ")";

    prepareDatagramWrapper(AbstractChatCore::INF_STATUS_CHNGD,
                           QChatSettings::settings()->broadcast().toIPv4Address(),
                           str);
  }

  qDebug("[ChatCore::slot_statusAnswer] ip = %s, ip = %u", QHostAddress(uid).toString().toLocal8Bit().data(), (int)uid);

  sendData(uid);
}
//\*****************************************************************************
void ChatCore::slot_infStatusChanged(const QString & name_id)
{
  addParametr("Channel", name_id.toUtf8());
  addParametr("Status" , QByteArray().append((char)m_myInfo->status(name_id)));

  prepareDatagramWrapper(AbstractChatCore::INF_STATUS_CHNGD,
                         QChatSettings::settings()->broadcast().toIPv4Address(),
                         QString("STATUS CHANGED")
                        );

  sendData(QChatSettings::settings()->broadcast().toIPv4Address());
}
//\*****************************************************************************
void ChatCore::slot_infoAnswer(const QString & name_id, quint64 uid, ChannelType type, uchar pics_ok)
{
  if(m_myInfo->status(name_id) == Globals::INVISIBLE)
    return;

  qDebug("\n[ChatCore::slot_infoAnswer] channel_name = %s, pics_ok = %d\n", name_id.toLocal8Bit().data(), pics_ok);

  QByteArray data;
  QBuffer    buffer;
  QImage*    pix;

  addParametr("LastName"   , m_myInfo->lastName   ().toUtf8());
  addParametr("FirstName"  , m_myInfo->firstName  ().toUtf8());
  addParametr("SecondName" , m_myInfo->secondName ().toUtf8());
  addParametr("DateOfBorn" , m_myInfo->dateOfBorn ().toString().toUtf8());
  addParametr("Address"    , m_myInfo->address    ().toUtf8());
  addParametr("HomePhone"  , m_myInfo->homePhone  ().toUtf8());
  addParametr("WorkPhone"  , m_myInfo->workPhone  ().toUtf8());
  addParametr("MobilePhone", m_myInfo->mobilePhone().toUtf8());
  addParametr("e-mail"     , m_myInfo->e_mail     ().toUtf8());
  addParametr("ICQ"        , m_myInfo->icq        ().toUtf8());
  addParametr("Homepage"   , m_myInfo->homepage   ().toUtf8());
  addParametr("AboutInfo"  , m_myInfo->aboutInfo  ().toUtf8());
  addParametr("Gender"     , QString().setNum(m_myInfo->gender()).toUtf8());
  addParametr("IpAddress"  , QString().setNum(m_settings->hostAddressOption("IP").toIPv4Address()).toUtf8());

  // TODO sdelat' nastraivaemyi razmer kartinok
  buffer.setBuffer(&data);

  if(pics_ok & 1)
    addParametr("PictureOk", QByteArray());
  else
  {
    if((pix = m_myInfo->newPictureImg()))
    {
//       if(pix->height() > 500)
//         pix->scaledToHeight(500).save(&buffer);
//       else
        pix->save(&buffer, "PNG", 100);

      delete pix;
    }

    addParametr("Picture"    , data);
    addParametr("PictureHash", m_myInfo->pictureHash());
  }

  buffer.close ();

  data.clear();
  buffer.setBuffer(&data);
  buffer.setData(QByteArray());

  if(pics_ok & 2)
    addParametr("PhotoOk", QByteArray());
  else
  {
    if((pix = m_myInfo->newPhotoImg()))
    {
//       if(pix->height() > 500)
//         pix->scaledToHeight(500).save(&buffer);
//       else
        pix->save(&buffer, "PNG", 100);
      delete pix;
    }
    addParametr("Photo"    , data);
    addParametr("PhotoHash", m_myInfo->photoHash());
  }

  addParametr("Status" , QByteArray().append((char)m_myInfo->status(name_id)));
  addParametr("Version", Globals::VersionStr.toUtf8());
  addParametr("Channel", name_id.toUtf8());
  addParametr("StatusDescription", UserInfo::myInfo()->statusDescription(name_id).toUtf8());

  prepareDatagramWrapper(AbstractChatCore::INFO_ANSWER, uid, QString(""), type);
  sendData(uid);
}
//\*****************************************************************************
void ChatCore::slot_requestFragments(char* fragments, quint32 len, quint32 id, quint64 uid)
{
  char buf[2];

  catUS2str(buf, len);

  qDebug("\n[ChatCore::slot_fragmentsRequest]:len = %lu, ID = %lu, addr = %s", (unsigned long)len, (unsigned long)id, QHostAddress(uid).toString().toLocal8Bit().data());

  clearParametrs();

  addParametr("FragmentsRequest", QByteArray(fragments, len));
  addParametr("FragmentsLen"    , QByteArray(buf      , 2));

  prepareDatagramWrapper(AbstractChatCore::FRAGMENTS_REQUEST, uid, QString(""), Common, id);

  sendData(uid);
}
//\*****************************************************************************
void ChatCore::slot_confirmReceivingFile(quint8 percents, quint16 dtgrmID, quint64 uid)
{
  prepareDatagramWrapper(AbstractChatCore::CONFIRM, uid, QString(""), Common, dtgrmID, percents);
  sendData(uid);
}
//\*****************************************************************************
void ChatCore::slot_acceptReceiving(quint16 dtgrmID, quint64 uid)
{
  qDebug("\n[ChatCore::slot_acceptReceiving] addr = %s", QHostAddress(uid).toString().toLocal8Bit().data());

  prepareDatagramWrapper(AbstractChatCore::ACCEPT, uid, QString(""), Common, dtgrmID, 0);
  sendData(uid);
}
//\*****************************************************************************
void ChatCore::slot_rejectReceiving(quint16 dtgrmID, quint64 uid, int reason)
{
  qDebug("\n[ChatCore::slot_rejectReceiving] addr = %s", QHostAddress(uid).toString().toLocal8Bit().data());

  prepareDatagramWrapper(AbstractChatCore::REJECT, uid, QString(""), Common, dtgrmID, reason);
  sendData(uid);
}
//\*****************************************************************************
void ChatCore::slot_privateChatRequest(const QString & name_id, quint64 uid)
{
  addParametr("Channel", name_id.toUtf8());
  prepareDatagramWrapper(AbstractChatCore::PRIVATE_CHAT_REQUEST, uid, QString(""));
  sendData(uid);
}

void ChatCore::processData()
{
  QString chnnl_name = QString().fromUtf8(getParametr("Channel", hdr()->parametrs));
  QString pl_name;

  UserInfo* info = m_chatWgt->findUser(hdr()->src_ip);
  quint64 src_ip = info ? info->ip() : 0;

  qDebug("[ChatCore::processData]: chnnl_name = %s", chnnl_name.toLocal8Bit().data());
  QDateTime dt;
  dt.setTime_t(hdr()->receive_tm);

  Message* msg = new Message(hdr(), src_ip);

  if(!filter()->validateMessage(msg))
  {
    delete msg;
    return;
  }

  delete msg;

  switch(hdr()->type)
  {
     case AbstractChatCore::SINGLE_MESSAGE :
     {
       SingleMessage* smsg = new SingleMessage(hdr(), src_ip);
       smsg->setIsIncoming(true);
       smsg->setNew();

       m_smhModel->addMessage(smsg);
       emit singleMessageIn(smsg, !getParametr("Important", hdr()->parametrs).isNull());
       break;
     }

     case AbstractChatCore::FILE:
       break;

     case AbstractChatCore::PRIVATE_CHAT_REQUEST:
       qDebug("[ChatCore::processData]: PRIVATE_CHAT_REQUEST chnnl_name = %s", chnnl_name.toLocal8Bit().data());
       privateChatRequest(chnnl_name, hdr()->src_ip);
       break;

     case AbstractChatCore::PLUGIN_DATA:
       pl_name = QString().fromUtf8(getParametr("PluginId", hdr()->parametrs));

       foreach(QObject* obj, m_chatWgt->m_allPlugins)
       {
         QChatWidgetPlugin* plug = qobject_cast<QChatWidgetPlugin*>(obj);
         QMap<QString, QByteArray> pars;

         if(plug)
           if(plug->name() == pl_name)
             plug->processData(getAllParametrs(hdr()->parametrs));
       }
       break;

     case AbstractChatCore::LOGIN_ACCEPTED:
       m_uid = hdr()->dest_ip;
       UserInfo::myInfo()->setNickname(hdr()->name);
       UserInfo::myInfo()->setUid(m_uid);

       if(m_loggedIn)
       {
         foreach(ChannelWgt* ch, m_chatWgt->channels())
           ch->sendStatusAnswer(0);
       }
       else
       {
         m_loggedIn = true;
         foreach(ChannelWgt* ch, m_chatWgt->channels())
         {
           ch->initChannel();
           ch->sendStatusAnswer(0);
         }
         emit loginFinished(0, hdr()->msg);
      }

     case AbstractChatCore::LOGIN_REJECTED:
       emit loginFinished(2, hdr()->msg);

     default:
     {
       emit chatDataReceived(hdr());
       newHdr();
       clearParametrs();
     }
  }
}
//\*****************************************************************************
void ChatCore::slot_processData(char* data, quint16 dataSize)
{
  setInputBuffer(data, dataSize);

  free(data);

  if(fillHeader())
    processData();
}
//\*****************************************************************************
void ChatCore::slot_processLargeData(LargeDatagram* dtgrm)
{
  Q_ASSERT(NULL != dtgrm);

  if(dtgrm->fillHeader(hdr()))
    processData();

  prepareDatagramWrapper(AbstractChatCore::FINISHED, dtgrm->destUid(), "", Common, dtgrm->id());

  sendData(dtgrm->destUid());
}
//\*****************************************************************************
void ChatCore::startThreads()
{
  qDebug("[ChatCore::startThreads]: begin");
  connect(this       , SIGNAL(wantSendFile(char*, quint16, const QString &, quint64, quint32)),
          m_sender   , SLOT  (addFileTask (char*, quint16, const QString &, quint64, quint32)));

  connect(this       , SIGNAL(wantSendLargeData(char*, quint16, char*, quint32, quint64, quint32)),
          m_sender   , SLOT  (addTask          (char*, quint16, char*, quint32, quint64, quint32)));

  m_sender  ->setPort(m_settings->intOption("OutputPort"));
  m_tcpReceiver->start();
  m_udpReceiver->start();
  m_sender     ->start();
  start();
  moveToThread(this);

//   sleep(1);

  initMode(Serverless, 1);

  qDebug("[ChatCore::startThreads]: end");
}
//\*****************************************************************************
void ChatCore::stopThreads()
{
  m_receiver->exit();
  m_sender->exit();
  exit();
}
//\*****************************************************************************
void ChatCore::slot_openSocketError(quint16 port)
{
  Globals::addError(QString("Couldn't open UDP socket on port %1 ").arg(port));
  qWarning("Couldn't open UDP socket on port %d\n", port);
}
//\*****************************************************************************
void ChatCore::slot_bindInputPort(int port)
{
  QChatSettings::settings()->setOption("InputPort", port);
  emit wantChangeInputPort(port);
}
//\*****************************************************************************
void ChatCore::slot_setNl(const QString & title, const QString & artist, const QString & album)
{
#if defined(Q_OS_LINUX)
  if(!title.isEmpty() || !artist.isEmpty() || !album.isEmpty())
  {
    m_nlTitle  = title;
    m_nlArtist = artist;
    m_nlAlbum  = album;

    m_nowListening = m_settings->strOption("NLFormat");
    m_nowListening.replace("%a", artist).replace("%b", album).replace("%t", title);

    m_nlIsNew = true;
  }
  else
    m_nowListening = "";

  m_settings->setNowListening(m_nowListening);

  if(m_settings->nlMode() & 2)
  {
    m_myInfo->setStatusDescription(m_chatWgt->currentChannelName(), m_nowListening);
    // FIXME incorrect works if other channel set its status in myInfo
    slot_statusAnswer(m_chatWgt->currentChannelName(), QChatSettings::settings()->broadcast().toIPv4Address(), Common, 0);
  }

#endif
}
//\*****************************************************************************
quint32 ChatCore::initSendingFile(quint64 uid, const QString & filename, QObject* ftw)
{
  clearParametrs();
  prepareDatagramWrapper(AbstractChatCore::FILE, uid);

  quint32 id = m_sender->getValidID();

  if(id)
  {
    qDebug("[ChatCore::sendFile]:str = %s", filename.right(filename.size() - 1 - filename.lastIndexOf("/")).toLocal8Bit().data());

    emit wantSendFile(header(), headerSize(), filename, uid, id);
    setNULLdataAndHeader();

    connect(m_receiver, SIGNAL(percentsConfirmed(quint8, quint16, quint64)),
            ftw       , SLOT  (slot_setProgress (quint8, quint16, quint64)));
    connect(m_receiver, SIGNAL(receivingRejected       (quint16)),
            ftw       , SLOT  (slot_rejectedByReceiver (quint16)));
    connect(m_receiver, SIGNAL(receivingCancelled      (quint16)),
            ftw       , SLOT  (slot_cancelledByReceiver(quint16)));
    connect(m_receiver, SIGNAL(receivingCancelled      (quint16)),
            m_sender  , SLOT  (slot_cancelTask         (quint16)));
    connect(m_sender  , SIGNAL(sendingCancelled        (quint16)),
            ftw       , SLOT  (slot_cannotSend         (quint16)));
    connect(m_sender  , SIGNAL(sendingFinished         (quint16)),
            ftw       , SLOT  (slot_sendingTimeout     (quint16)));
    connect(ftw       , SIGNAL(rejected            (quint16, quint64, int)),
            this      , SLOT  (slot_rejectReceiving(quint16, quint64, int)));// slot_rejectReceiving, but here it working such 'cancel Sending' :)
    connect(ftw       , SIGNAL(cancel         (quint16)),
            m_sender  , SLOT  (slot_cancelTask(quint16)));
  }

  return id;
}
//\*****************************************************************************
void ChatCore::initReceivingFile(QObject* obj)
{
  connect(m_receiver, SIGNAL(percentsRemain      (quint8, quint16, quint64)),
          obj       , SLOT  (slot_setProgress    (quint8, quint16, quint64)));

  connect(obj       , SIGNAL(accepted            (QString, quint16, quint64)),
          m_receiver, SLOT  (slot_acceptDatagram (QString, quint16, quint64)));

  connect(obj       , SIGNAL(rejected            (quint16, quint64, int)),
          m_receiver, SLOT  (slot_rejectDatagram (quint16, quint64)));

  connect(m_receiver, SIGNAL(readyReceive          (quint16, quint64)),
          this      , SLOT  (slot_acceptReceiving  (quint16, quint64)));

  connect(m_receiver, SIGNAL(sendingCancelled      (quint16, quint64)),
          obj       , SLOT  (slot_cancelledBySender(quint16, quint64)));

  connect(obj       , SIGNAL(rejected            (quint16, quint64, int)),
          this      , SLOT  (slot_rejectReceiving(quint16, quint64, int)));

  connect(m_receiver, SIGNAL(percentsRemain           (quint8, quint16, quint64)),
          this      , SLOT  (slot_confirmReceivingFile(quint8, quint16, quint64)));

  connect(m_receiver, SIGNAL(receivingTimeout     (quint16, quint64)),
          obj       , SLOT  (slot_receivingTimeout(quint16, quint64)));
}
//\*****************************************************************************
void ChatCore::slot_loadProfile(const QString & name)
{
  UserProfile* profile = m_profiles[name];
  QByteArray   data;
  QImage       pix;

  if(profile)
  {
    qDebug("[ChatCore::loadProfile]: %s", name.toLocal8Bit().data());

    m_currentProfile = profile;

    m_myInfo   = profile->info();
    m_settings = profile->prefs();

    m_myInfo->setCompName(QHostInfo::localHostName());

    UserInfo::setMyInfo(m_myInfo);
    QChatSettings::setSettings(m_settings);
  }

  else
  {
    m_myInfo   = new UserInfo;
    m_settings = new QChatSettings;
    profile    = new UserProfile(name, m_settings, m_myInfo);

    m_currentProfile = profile;

    UserInfo::setMyInfo(m_myInfo);
    QChatSettings::setSettings(m_settings);

    m_profiles.insert(name, profile);

    m_myInfo->setNickname(name);
  }

  QChatSettings::setProfileName(name);

  emit profileLoaded(name);
}
//\*****************************************************************************
QStringList ChatCore::profiles() const
{
  QStringList profiles;
  QMapIterator<QString, UserProfile*> i(m_profiles);

  while (i.hasNext())
  {
    i.next();
    profiles << i.key();
  }

  return profiles;
}
//\*****************************************************************************
const QString & ChatCore::currentProfile() const
{
  return m_currentProfile->name();
}
//\*****************************************************************************
void ChatCore::slot_renameProfile(const QString & old_name, const QString & new_name)
{
  UserProfile* profile = m_profiles[old_name];

  if(profile)
  {
    profile->rename(new_name);
    m_profiles.erase(m_profiles.find(old_name));
    m_profiles.insert(new_name, profile);
  }
}

void ChatCore::slot_deleteProfile(const QString & name)
{
  m_profiles.erase(m_profiles.find(name));

  delete m_currentProfile->info();
  delete m_currentProfile->prefs();
  delete m_currentProfile;

  slot_loadProfile(m_profiles.begin().key());
}

void ChatCore::processSmiles(QString msg) // TODO perepisat'
{
  Smile*        smile;
  QList<Smile*> smiles;
  QString       sm_str;
  QString msg_ = msg;
  int idx_end = 0;
  QString str;

  smile   = 0;
  sm_str  = ChatTextWgt::nextSmile(msg, &smile);
  idx_end = msg.indexOf(sm_str);

  for(; idx_end != -1 && !sm_str.isEmpty();)
  {
    if(smile)
    {
      if(sm_str != smile->smiles[0])
      {
        msg_.replace(sm_str, smile->smiles[0]);
        sm_str = smile->smiles[0];
      }

      if(!smiles.contains(smile))
      {
        smiles.append(smile);
        m_smilesParam.append(sm_str.toUtf8());
        m_smilesParam.append((char)0);
      }
    }

    str = msg.left(idx_end);
    msg = msg.right(msg.size() - idx_end - sm_str.size());

    sm_str   = ChatTextWgt::nextSmile(msg, &smile);
    if(sm_str.isEmpty())
      break;

    idx_end = msg.indexOf(smile->smiles[0]);
  }

  addParametr("Smiles", m_smilesParam);

  QList<Smile*>::const_iterator i;

  for (i = smiles.constBegin(); i != smiles.constEnd(); ++i)
  {
    str = QChatSettings::settings()->smilesThemePath() + (*i)->name;
    if(QFile(str).exists())
      addParametr(QString("Smile") + (*i)->smiles[0], str);
    else if(QFile(str + ".png").exists())
      addParametr(QString("Smile") + (*i)->smiles[0], str + ".png");
    else if(QFile(str + ".gif").exists())
      addParametr(QString("Smile") + (*i)->smiles[0], str + ".gif");
    else if(QFile(str + ".jpg").exists())
      addParametr(QString("Smile") + (*i)->smiles[0], str + ".jpg");
  }
}

void ChatCore::slot_msgsHistoryAnswer(const QString & ch_name_id, quint64 uid, const QByteArray & msgs, AbstractChatCore::ChannelType ch_type)
{
  addParametr("Channel"    , ch_name_id.toUtf8());
  addParametr("MsgsHistory", msgs);

  prepareDatagramWrapper(AbstractChatCore::MSGS_HISTORY_ANSWER, uid, QString(""), ch_type);

  sendData(uid);
}

void ChatCore::slot_msgsNumAnswer(const QString & ch_name_id, quint64 uid, quint32 msgs_num, AbstractChatCore::ChannelType type)
{
  addParametr("Channel", ch_name_id.toUtf8());
  addParametr("MsgsNum", QString().setNum(msgs_num).toUtf8());

  prepareDatagramWrapper(AbstractChatCore::MSGS_NUM_ANSWER, uid, QString(""), type);

  sendData(uid);
}

void ChatCore::slot_sendMessage(const QString & ch_name_id, quint64 uid, AbstractChatCore::ChannelType ch_type, QTextDocument* doc)
{
  QString fname;
  QString smname;
  int sm_num = 0;
  QTextCursor cur(doc);
  QMap<QString, int> smiles;


  m_smilesParam.clear();

  for(QTextBlock it = doc->begin(); it != doc->end(); it = it.next())
  {
    cur = QTextCursor(it);

    QTextBlock::iterator i = cur.block().begin();
    for (; i != cur.block().end(); i++)
    {
      QTextFormat fmt = i.fragment().charFormat();
      if(fmt.isImageFormat())
      {
        fname = fmt.stringProperty(QTextFormat::ImageName);

        if(!smiles.contains(fname))
        {
          smname = QString().setNum(sm_num) + QString(QChar(0xFFFC + 1));

          m_smilesParam.append(smname.toUtf8());
          m_smilesParam.append((char)0);

          smiles.insert(fname, sm_num);

          sm_num++;

          addParametr("Smile" + smname, fname);
        }
        else
          smname = QString().setNum(smiles.value(fname)) + QString(QChar(0xFFFC + 1));

        cur.beginEditBlock();

        cur.clearSelection();
        cur.setPosition(i.fragment().position());
        cur.deleteChar();
        cur.insertText(smname);

        cur.endEditBlock();

        i = cur.block().begin();
      }
    }
  }

  if(protocolVersion() >= 4)
    slot_prepareAndSend(ch_name_id, uid, AbstractChatCore::MESSAGE, doc->toHtml(), ch_type, NULL);
  else
    slot_prepareAndSend(ch_name_id, uid, AbstractChatCore::MESSAGE, doc->toPlainText(), ch_type, NULL);

  doc->deleteLater();
}

void ChatCore::slot_avatarAnswer(const QString & ch_name_id, quint64 uid, AbstractChatCore::ChannelType channel_type)
{
  if(m_myInfo->status(ch_name_id) == Globals::INVISIBLE)
    return;

  QByteArray data;
  QBuffer    buffer;
  const QImage* icon = m_myInfo->newIconImg();

  buffer.setBuffer(&data);

  if(icon)
  {
    icon->save(&buffer, "PNG", 100);
    delete icon;
  }

  addParametr("Channel", ch_name_id.toUtf8());
  addParametr("Icon", data);
  addParametr("IconHash", UserInfo::myInfo()->avatarHash());

  prepareDatagramWrapper(AbstractChatCore::AVATAR_ANSWER,
                  uid,
                  QString(""),
                  channel_type);

  sendData(uid);
}

void ChatCore::sendPluginData(const QString & plugin_id , const QMap<QString, QByteArray> & parametrs,
                              quint64 uid               , AbstractChatCore::DataType data_type,
                              const QString & channel_id, uint ch_type, const QString & msg)
{
  foreach(QString name, parametrs.keys())
    addParametr(name, parametrs[name]);

  addParametr("PluginId", plugin_id.toUtf8());

  slot_prepareAndSend(channel_id, uid, data_type, msg, (AbstractChatCore::ChannelType)ch_type);
}

void ChatCore::prepareDatagramWrapper(AbstractChatCore::DataType dtgrm_type, quint64 dest_uid, const QString & msg, AbstractChatCore::ChannelType chnnl_type, quint64 dtgrm_id, quint64 dtgrm_num)
{
  // FIXME krivovato sdelano..
  //*****
  quint64 src_ip;
  quint64 dest_ip = dest_uid;

  // in serverless mode we using IP addresses but in Server - UIDs
  if(m_mode == Serverless)
    src_ip  = m_settings->hostAddressOption("IP").toIPv4Address();
  else
    src_ip  = m_uid;
  //*****
  prepareDatagram(dtgrm_type,
                  dest_ip,

                  m_myInfo->nickname(),
                  m_myInfo->compName(),
                  src_ip,

                  msg,
                  chnnl_type,
                  dtgrm_id,
                  dtgrm_num);
}

void ChatCore::initMode(Mode mode_, bool first)
{
  m_settings->setMode((QChatSettings::Mode)mode_);
  m_mode = mode_;

  if(!first)
  {
    disconnect(m_receiver , SIGNAL(dataReceived    (char *, quint16)),
                      this, SLOT  (slot_processData(char *, quint16)));

    disconnect(m_receiver , SIGNAL(largeDataReceived    (LargeDatagram*)),
                      this, SLOT  (slot_processLargeData(LargeDatagram*)));

    disconnect(m_receiver , SIGNAL(wantFragments        (char*, quint32, quint32, quint64)),
                      this, SLOT  (slot_requestFragments(char*, quint32, quint32, quint64)));

    disconnect(m_receiver , SIGNAL(     fragmentsRequest(char*, quint32)),
                 m_sender , SLOT  (slot_fragmentsRequest(char*, quint32)));

    disconnect(m_receiver , SIGNAL(     receivingAccepted(quint16)),
                 m_sender , SLOT  (slot_acceptSending    (quint16)));

    disconnect(m_receiver , SIGNAL(openSocketError(quint16)),
                     this , SLOT  (slot_openSocketError(quint16)));

    disconnect(m_receiver , SIGNAL(dtgrmFinished  (quint16)),
               m_sender   , SLOT  (slot_cancelTask(quint16)));

    disconnect(this       , SIGNAL(wantChangeInputPort(quint16)),
               m_receiver , SLOT  (changePort         (quint16)));
  }
  else
  {
    connect(this         , SIGNAL(wantLogin(const QString&, const QHostAddress&, uint, const QString&)),
            m_tcpReceiver, SLOT  (login    (const QString&, const QHostAddress&, uint, const QString&)));
//     connect(m_tcpReceiver, SIGNAL(loginFinished(int, const QString&)),
//             this         , SIGNAL(loginFinished(int, const QString&)));

  }

  switch(mode_)
  {
    case Server :
      changeProtocolVersion(4);
      m_receiver = m_tcpReceiver;
      m_sender->useTcp(m_tcpReceiver->socket());
      break;

    case Serverless :
      m_receiver = m_udpReceiver;
      m_sender->useUdp();
      break;

    default:
      m_receiver = m_udpReceiver;
      m_sender->useUdp();
  }

  createReceiverConnections();

  emit wantChangeInputPort(m_settings->intOption("InputPort"));
}

void ChatCore::createReceiverConnections()
{
  connect(m_receiver , SIGNAL(dataReceived    (char *, quint16)),
                 this, SLOT  (slot_processData(char *, quint16)));

  connect(m_receiver , SIGNAL(largeDataReceived    (LargeDatagram*)),
                 this, SLOT  (slot_processLargeData(LargeDatagram*)));

  connect(m_receiver , SIGNAL(wantFragments        (char*, quint32, quint32, quint64)),
                 this, SLOT  (slot_requestFragments(char*, quint32, quint32, quint64)));

  connect(m_receiver , SIGNAL(     fragmentsRequest(char*, quint32)),
            m_sender , SLOT  (slot_fragmentsRequest(char*, quint32)));

  connect(m_receiver , SIGNAL(     receivingAccepted(quint16)),
            m_sender , SLOT  (slot_acceptSending    (quint16)));

  connect(m_receiver , SIGNAL(openSocketError(quint16)),
                this , SLOT  (slot_openSocketError(quint16)));

  connect(m_receiver , SIGNAL(dtgrmFinished  (quint16)),
          m_sender   , SLOT  (slot_cancelTask(quint16)));

  connect(this       , SIGNAL(wantChangeInputPort(quint16)),
          m_receiver , SLOT  (changePort         (quint16)));

}

void ChatCore::slot_setMode() // Server or Serverless
{

}

void ChatCore::slot_login(const QHostAddress & addr, const QString & nick)
{
  // FIXME !!!
  emit wantLogin(nick, addr, 61100, "");
}

void ChatCore::disconnectFromServer()
{
  m_tcpReceiver->socket()->disconnectFromHost();
  m_loggedIn = false;
}

void ChatCore::changeLogin(const QString & newLogin)
{
  prepareDatagram(AbstractChatCore::CHANGE_LOGIN, 1, newLogin, UserInfo::myInfo()->compName(), UserInfo::myInfo()->uid(), "");

  sendData(1);
}

void ChatCore::initPlugins()
{
  m_pluginManager->getPlugins();

      connect(m_pluginManager,
              SIGNAL(sendData(const QString &, const QMap<QString, QByteArray> &, quint64,
                              AbstractChatCore::DataType, const QString &, uint, const QString &)),
              this,
              SLOT  (sendPluginData(const QString &, const QMap<QString, QByteArray> &, quint64,
                              AbstractChatCore::DataType, const QString &, uint, const QString &)));
}

UserInfo* ChatCore::getUserInfo(quint64 uid)
{
  UserWgt* user;

  foreach(ChannelWgt* cw, m_chatWgt->channels())
    if(cw->name() == "Main")
      if((user = cw->findUser(uid)))
        return user->info();

  return NULL;
}

void ChatCore::changeProtocolVersion(uint ver)
{
  m_settings->setOption("ProtocolVersion", ver);
  setProtocolVersion(ver);
}

bool ChatCore::loggedIn()
{
  return (m_mode == Server && m_loggedIn);
}

void ChatCore::slot_disconnectedFromServer()
{
  m_loggedIn = false;
  emit disconnectedFromServer();
}

#include "chatcore_settings.cpp"
