/***************************************************************************
 *   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 "largedatagram.h"
#include "globals.h"

#include <assert.h>

#include <QHostAddress>

#include "chatcore.h"
#include "userinfo.h"

LargeDatagram::LargeDatagram(quint64 IP, quint32 ID, QObject* parent) :
  QObject(parent),
  m_remain             (101),
  m_isFile             (false),
  m_fileInited         (false),
  m_inited             (false),
  m_srcIP              (IP),
  m_datagramID         (ID),
  m_lastFragmentNum    (0),
  m_fragments          (NULL),
  m_data               (NULL),
  m_requestInterval    (1000),
  m_selfDestroyInterval(60 * 1000)
{
  qDebug("[LargeDatagram::LargeDatagram]: ID = %lu", (unsigned long)ID);

  m_requestTimer        = new QTimer(this);
  m_selfDestroyTimer    = new QTimer(this);

  connect(m_requestTimer    , SIGNAL(timeout()), this, SLOT(slot_fragmentsRequest ()));
  connect(m_selfDestroyTimer, SIGNAL(timeout()), this, SLOT(slot_selfDestroy      ()));
}
//\*****************************************************************************
LargeDatagram::~LargeDatagram()
{
  qDebug("[~LargeDatagram]: ID = %lu", (unsigned long)m_datagramID);
  free(m_fragments);
  free(m_data);
}
//\*****************************************************************************
void LargeDatagram::initDatagram(const char* dtgrm, quint32 dtgrm_len)
{
  quint8 cnl; // comp_name_len
  quint8 unl; // user_name_len

  if(m_inited || dtgrm_len < AbstractChatCore::protocolLen())
    return;

  m_programVersion    = ChatCore::programVersion (dtgrm);
  m_protocolVersion   = ChatCore::protocolVersion(dtgrm);
  m_destIP            = ChatCore::destIp         (dtgrm);
  m_packetType        = ChatCore::packetType     (dtgrm);
  m_fragmentSize      = ChatCore::fragmentSize   (dtgrm);
  m_firstFragmentTime = ChatCore::time           (dtgrm);
  m_channelType       = ChatCore::channelType    (dtgrm);
  cnl                 = ChatCore::compNameLen    (dtgrm);
  unl                 = ChatCore::userNameLen    (dtgrm);

  if(dtgrm_len < quint32(AbstractChatCore::protocolLen() + unl + cnl + AbstractChatCore::optionsLen(dtgrm)))
    return;

  m_totalFragments    = ChatCore::messageLen  (dtgrm);
  m_totalSize         = ChatCore::parametrsLen(dtgrm);
  m_senderCompName    = ChatCore::compName    (dtgrm, dtgrm_len);
  m_senderName        = ChatCore::userName    (dtgrm, dtgrm_len);

  // не m_totalFragments-1, потому что 0-й фрагмент является инициализирующим
  m_lastFragmentNum   = m_totalFragments;

  // вычисляем есть ли остаток
  if(m_totalSize < m_fragmentSize * m_totalFragments)
    m_lastFragmentSize  = m_totalSize - (m_fragmentSize * (m_totalFragments - 1));
  else
    m_lastFragmentSize  = m_fragmentSize;

  m_currentSize     = 0;
  m_fragmentsRemain = m_totalFragments;
  m_fragments       = (char*)calloc(m_totalFragments, 1);
  assert(NULL != m_fragments);

  //******************
  if(AbstractChatCore::packetType(dtgrm) == AbstractChatCore::FILE)
  {
    QByteArray ba;
    quint16    size;
    if(dtgrm_len >= quint32(AbstractChatCore::protocolLen() + unl + cnl + 2 + AbstractChatCore::optionsLen(dtgrm)))
    {
      size = str2US(dtgrm + AbstractChatCore::protocolLen() + unl + cnl + AbstractChatCore::optionsLen(dtgrm));

      if(dtgrm_len >= quint32(AbstractChatCore::protocolLen() + unl + cnl + 2 + size + AbstractChatCore::optionsLen(dtgrm)))
      {
        m_filename = QString().fromUtf8(dtgrm + AbstractChatCore::protocolLen() + unl + cnl + 2, size + AbstractChatCore::optionsLen(dtgrm)); // FIXME kraine koryavo
        qDebug("[LargeDatagram::initDatagram]: filename = %s", m_filename.toLocal8Bit().data());
      }

      m_isFile = true;
    }
    else
    {
      m_selfDestroyTimer->setInterval(m_selfDestroyInterval);
      m_selfDestroyTimer->start();
      return;
    }
  }
  //******************
  else
  {
    m_data = (char*)calloc(m_totalSize, 1);
    assert(NULL != m_data);
  }

  m_inited = true;

  if((!m_isFile || m_fileInited) && m_requestTimer && m_selfDestroyTimer)
  {
    m_requestTimer->setInterval(m_requestInterval);
    m_requestTimer->start();
    m_selfDestroyTimer->setInterval(m_selfDestroyInterval);
    m_selfDestroyTimer->start();
  }
  else if(!m_requestTimer || !m_selfDestroyTimer)
  {
    Globals::addError("Timer(s) is(are) NULL!");
    qWarning("[LargeDatagram[%d]::initDatagram]: Timer(s) is(are) NULL! req_timer = %p, destr_timer = %p\n ", m_datagramID, m_requestTimer, m_selfDestroyTimer);
  }
}
//\*****************************************************************************
void LargeDatagram::addFragment(const char* dtgrm, quint32 dtgrm_len)
{
//   qDebug("[LargeDatagram[%d]::addFragment]:", m_datagramID);

  // TODO proveryat' sootvetstvie dtgrm_len neobhodimomu razmeru
  if(!m_inited || !m_fragmentsRemain || dtgrm_len < AbstractChatCore::protocolLen())
    return;

  quint32 num = ChatCore::packetNum  (dtgrm);
  quint8  cnl = ChatCore::compNameLen(dtgrm);
  quint8  unl = ChatCore::userNameLen(dtgrm);

  if((dtgrm_len < quint32(AbstractChatCore::protocolLen() + unl + cnl + AbstractChatCore::optionsLen(dtgrm))) || m_fragments[num - 1] || (ChatCore::packetType(dtgrm) == AbstractChatCore::FILE))
    return;

  // если фрагмент последний - его размер может быть меньше чем у остальных.. выясним это:
  int fragment_size = (num != m_totalFragments) ? m_fragmentSize : m_lastFragmentSize;

  if(dtgrm_len < quint32(AbstractChatCore::protocolLen() + unl + cnl + fragment_size + AbstractChatCore::optionsLen(dtgrm)))
      return;

  memcpy((m_data + (num - 1) * m_fragmentSize), dtgrm + AbstractChatCore::protocolLen() + cnl + unl + AbstractChatCore::optionsLen(dtgrm), fragment_size);
  m_fragmentsRemain--;
  m_currentSize += fragment_size;
  m_fragments[num - 1] = 1;

  if(!m_fragmentsRemain)
  {
    m_finalFragmentTime  = time(NULL);

    m_requestTimer->stop();
    m_selfDestroyTimer->stop();

    delete m_selfDestroyTimer;
    delete m_requestTimer;
    m_selfDestroyTimer = NULL;
    m_requestTimer = NULL;

    return;
  }
  else if(num == m_lastFragmentNum)
  {
    qDebug("[LargeDatagram[%d]::addFragment]: fragmentsRemain  = %d", m_datagramID, m_fragmentsRemain);
    qDebug("[LargeDatagram[%d]::addFragment]: lastFragmentNum = %d", m_datagramID, m_lastFragmentNum);

    for(int i = m_lastFragmentNum; i >= 0; i--)
      if(!m_fragments[i])
      {
        m_lastFragmentNum = i;
        break;
      }

    qDebug("[LargeDatagram[%d]::addFragment]: newLastFragmentNum = %d", m_datagramID, m_lastFragmentNum);

    emit wantFragments(m_fragments, m_totalFragments, m_datagramID, m_srcIP);
  }

  if(m_requestTimer)
    m_requestTimer->start(m_requestInterval);

  if(m_selfDestroyTimer)
    m_selfDestroyTimer->start(m_selfDestroyInterval);
}
//\*****************************************************************************
void LargeDatagram::addFileFragment(const char* dtgrm, quint32 dtgrm_len)
{
  if(!m_inited || !m_fragmentsRemain || dtgrm_len < AbstractChatCore::protocolLen() || (m_isFile && !m_fileInited))
    return;

  quint32 num = ChatCore::packetNum  (dtgrm);
  quint8  cnl = ChatCore::compNameLen(dtgrm);
  quint8  unl = ChatCore::userNameLen(dtgrm);

  if(dtgrm_len < quint32(AbstractChatCore::protocolLen() + unl + cnl + AbstractChatCore::optionsLen(dtgrm)))
    return;

  if(m_fragments[num - 1])
    return;

  // если фрагмент последний - его размер может быть меньше чем у остальных.. выясним это:
  int fragment_size = (num != m_totalFragments) ? m_fragmentSize : m_lastFragmentSize;

  if(dtgrm_len < quint32(AbstractChatCore::protocolLen() + unl + cnl + fragment_size + AbstractChatCore::optionsLen(dtgrm)))
    return;

  m_file.seek(m_fragmentSize * (num - 1));
  m_file.write(dtgrm + AbstractChatCore::protocolLen() + cnl + unl + AbstractChatCore::optionsLen(dtgrm), fragment_size);
  m_fragmentsRemain--;
  m_currentSize += fragment_size;
  m_fragments[num - 1] = 1;

  if(!m_fragmentsRemain)
  {
    m_finalFragmentTime  = time(NULL);

    printf("[LargeDatagram[%d]::addFileFragment]: 0%% left\n", m_datagramID);

    emit percentsRemain(0, m_datagramID, m_srcIP);
    m_file.close();
    m_requestTimer->stop();
    m_selfDestroyTimer->stop();
    delete m_selfDestroyTimer;
    delete m_requestTimer;

    m_selfDestroyTimer = NULL;
    m_requestTimer = NULL;
    emit completed(this);
    return;
  }
  else if(num == m_lastFragmentNum)
  {
    qDebug("[LargeDatagram[%d]::addFileFragment]: fragmentsRemain = %d", m_datagramID, m_fragmentsRemain);
    qDebug("[LargeDatagram[%d]::addFileFragment]: lastFragmentNum = %d", m_datagramID, m_lastFragmentNum);

    for(int i = m_lastFragmentNum; i >= 0; i--)
      if(!m_fragments[i])
      {
        m_lastFragmentNum = i;
        break;
      }

    qDebug("[LargeDatagram[%d]::addFileFragment]: newLastFragmentNum = %d", m_datagramID, m_lastFragmentNum);

    emit wantFragments(m_fragments, m_totalFragments, m_datagramID, m_srcIP);
  }

  quint8 remain = (quint8)((double)m_fragmentsRemain / ((double)m_totalFragments / (double)100));
  if(remain < m_remain)
  {
    m_remain = remain;

    printf("[LargeDatagram[%d]::addFileFragment]: %d%% left\n", m_datagramID, remain);

    emit percentsRemain(remain, m_datagramID, m_srcIP);
  }

  if(m_requestTimer)
    m_requestTimer->start(m_requestInterval);

  if(m_selfDestroyTimer)
    m_selfDestroyTimer->start(m_selfDestroyInterval);
}
//\*****************************************************************************
bool LargeDatagram::fillHeader(QC_DatagramHeader* Hdr)
{
  qDebug("[LargeDatagram(%lu, %lu)::fillHeader], m_inited = %d", (unsigned long)m_srcIP, (unsigned long)m_datagramID, m_inited);
  int shift;

  shift = /*m_protocolVersion >= 4 ? 0 :*/ 8;

  if(m_inited == false || m_programVersion < 5 || (m_protocolVersion != AbstractChatCore::protocolVersion()))
    return 0;

  Hdr->programVersion  = m_programVersion;
  Hdr->protocolVersion = m_protocolVersion;
  Hdr->dest_ip         = m_destIP;
  Hdr->src_ip          = m_srcIP;
  Hdr->type            = m_packetType;
  Hdr->tm              = m_firstFragmentTime;
  Hdr->receive_tm      = m_finalFragmentTime;
  Hdr->name            = m_senderName;
  Hdr->comp_name       = m_senderCompName;
  Hdr->chnnl_id        = m_channelType;

  Hdr->msg_len         = str2UL(m_data    );
  Hdr->parametrs_len   = str2UL(m_data + 4);

  printf("protver = %d\n", m_protocolVersion);

//   if(m_protocolVersion >= 4)
//   {
//     Hdr->msg_len         = AbstractChatCore::messageLen(m_data);
//     Hdr->parametrs_len   = AbstractChatCore::parametrsLen(m_data);
//   }

  if(m_totalSize < shift + Hdr->msg_len + Hdr->parametrs_len)
  {
    qWarning("[LargeDatagram::fillHeader]: [error] wrong length of fragmented packet (total size smaller than expected[%jd < %lu]) [3]\n Exiting from fillHeader\n", m_totalSize, shift + Hdr->msg_len + Hdr->parametrs_len );
//     qWarning("[LargeDatagram::fillHeader]: %d + %d\n", Hdr->msg_len, Hdr->parametrs_len);
    return 0;
  }

  Hdr->msg       = QString().fromUtf8( QByteArray(m_data  + shift, Hdr->msg_len), Hdr->msg_len );
  Hdr->parametrs = QByteArray(m_data + shift + Hdr->msg_len, Hdr->parametrs_len);

  Hdr->color = ChatCore::getColorParametr(&Hdr->parametrs);

  // getting versionName
  QByteArray ba = ChatCore::getParametr("Version", Hdr->parametrs);

  if(!ba.isEmpty())
    Hdr->versionName = QString().fromUtf8(ba);
  else if(Hdr->programVersion <= Globals::VersionID)
    Hdr->versionName = QString(Globals::VersionsTable[Hdr->programVersion - 1]);
  else
    Hdr->versionName = QString("New Version[id = %1]").arg(Hdr->programVersion);

  // getting status
  Hdr->status = Globals::FREE;
  ba = ChatCore::getParametr("Status", Hdr->parametrs);
  if(!ba.isEmpty())
    Hdr->status = ba[0];

  clear();

  emit completed(this);

  return 1;
}
//\*****************************************************************************
void LargeDatagram::clear()
{
  if(m_inited)
  {
    free(m_data);
    free(m_fragments);
    m_data = NULL;
    m_fragments = NULL;
  }
  m_inited = false;
}
//\*****************************************************************************
void LargeDatagram::slot_fragmentsRequest()
{
  qDebug("[LargeDatagram[%d]::slot_fragmentsRequest]", m_datagramID);

  if(m_inited && m_fragmentsRemain)
    emit wantFragments(m_fragments, m_totalFragments, m_datagramID, m_srcIP);
}
//\*****************************************************************************
void LargeDatagram::slot_selfDestroy()
{
  qDebug("[LargeDatagram[%d]::selfDestroy]", m_datagramID);

  delete m_requestTimer;
  delete m_selfDestroyTimer;

  clear();
  m_inited = false;

  emit wantDie(this);
}
//\*****************************************************************************
void LargeDatagram::allocateSpace()
{
  if(!m_fileInited)
    return;

  m_file.open(QIODevice::WriteOnly);
  m_file.resize(m_totalSize);
  m_file.seek(0);
}
//\*****************************************************************************
void LargeDatagram::slot_initFile(const QString & filename)
{
  m_file.setFileName(filename);
  m_fileInited = true;

  allocateSpace();

  m_requestTimer->setInterval(m_requestInterval);
  m_requestTimer->start();

  emit readyReceive(m_datagramID, m_srcIP);
}
//\*****************************************************************************
