/***************************************************************************
 *   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 "largedatagramout.h"

#include <QThread>

#include "chatcore.h"

LargeDatagramOut::LargeDatagramOut(QObject *parent)
 : QObject(parent),
  m_confirmed          (false),
  m_inited             (false),
  m_header             (NULL),
  m_data               (NULL),
  m_fragments          (NULL),
  m_selfDestroyInterval(10 * 1000)
{
  m_selfDestroyTimer = new QTimer(this);

  connect(m_selfDestroyTimer, SIGNAL(timeout()), this, SLOT(selfDestroy()));
}
//\*****************************************************************************
LargeDatagramOut::~LargeDatagramOut()
{
  qDebug("[~LargeDatagramOut]: ID = %lu", (unsigned long)m_id);
  free(m_header); // we need free only m_header beckause m_data points to m_header + m_headerSize
  free(m_fragments);
}
//\*****************************************************************************
void LargeDatagramOut::init(char* header, quint16 header_size,
                            char* data  , quint32 data_size  , quint64 dest_uid, quint32 id)
{
  m_id              = id;
  m_header          = header;
  m_data            = data  ;
  m_headerSize      = header_size;
  m_dataSize        = data_size;
  m_sizePerFragment = (MAX_PACKET_LEN - m_headerSize);
  m_rest            = m_dataSize % m_sizePerFragment;
  m_totalFragments  = m_dataSize / m_sizePerFragment + m_rest;
  m_fragmentsRemain = m_totalFragments;
  m_fragments       = (char*)calloc(m_totalFragments + 1, 1);// + 1, beckause we need to send initialization packet
  m_inited          = true;
  m_confirmed       = true;
  m_destUid         = dest_uid;
}
//\*****************************************************************************
void LargeDatagramOut::init(char*   header, quint16 header_size,
                            const QString & filename, quint64 dest_uid, quint32 id)
{
  m_id         = id;
  m_header     = header;
  m_data       = NULL;
  m_headerSize = header_size;
  m_dataSize   = 0;
  m_filename   = filename;
  m_destUid    = dest_uid;

  m_file.setFileName(filename);

  if(!m_file.open(QIODevice::ReadOnly))
  {
    qWarning("[LargeDatagramOut::init]: Failed. couldn't open file %s for reading!\n", m_filename.toLocal8Bit().data());
    emit wantDie(this);
    emit sendingCancelled(id);
    return;
  }

  if(m_file.handle() < 0)
  {
    qWarning("[LargeDatagramOut::init]: m_file.handle() < 0\n");
//     emit wantDie(this);
//     return;
  }

  m_fileSize        = m_file.size();
  m_sizePerFragment = (MAX_PACKET_LEN - m_headerSize);
  m_rest            = m_fileSize % m_sizePerFragment;
  m_totalFragments  = m_fileSize / m_sizePerFragment + m_rest;
  m_fragmentsRemain = m_totalFragments;
  m_fragments       = (char*)calloc(m_totalFragments + 1, 1);// + 1, t.k. nado otoslat' esche i initsializir. paket!
  m_inited          = true;
}
//\*****************************************************************************
void LargeDatagramOut::prepareInitHeader()
{
  if(!m_inited)
    return;

  AbstractChatCore::setPacketId    (m_header, m_id);
  AbstractChatCore::setFragmentSize(m_header, m_sizePerFragment);
  AbstractChatCore::setPacketNum   (m_header, 0);
  AbstractChatCore::setMessageLen  (m_header, m_totalFragments); // +61 MsgLen        / TotalFragments / DataLen

  // FIXME !!!file size may be greater then quint32!!!
  if(!m_filename.isEmpty())
    AbstractChatCore::setParametrsLen(m_header, m_fileSize);
  else
    AbstractChatCore::setParametrsLen(m_header, m_dataSize); // +65 ParametersLen / TotalSize      / == 0
}
//\*****************************************************************************
void LargeDatagramOut::sendNextFragment(QAbstractSocket* socket, quint16 port, char* buf)
{
  quint32 num;
  quint32 i;
  quint32 size;
  int res = -1;

  QTcpSocket* tcp = qobject_cast<QTcpSocket*>(socket);
  QUdpSocket* udp = qobject_cast<QUdpSocket*>(socket);


  if(!m_fragmentsRemain)
  {
    if(m_selfDestroyTimer && (m_selfDestroyTimer->timerId() < 0))
    {
      m_selfDestroyTimer->setSingleShot(true);
      m_selfDestroyTimer->start(m_selfDestroyInterval);
    }
    return;
  }

  // TODO optimizirovat'
  for(i = 0; i <= m_totalFragments; i++)
    if(m_fragments[i] == 0)
      break;

  num = i;

  if(num != 0 && !m_confirmed)
    return;

  if(num > m_totalFragments)// znachit net neotoslannyh fragmentov. i soobschenii o peresylke ne prihodilo
    return;

  size = writeFragment(buf, num);

  if(tcp)
  {
    AbstractChatCore::setPacketSize(buf, size);

    res = tcp->write(buf, size);
    tcp->flush();
  }
  else if(udp)
    res = udp->writeDatagram(buf, size, QHostAddress(m_destUid), port);

  if(res < 0)
    qWarning("\n[LargeDatagramOut::sendNextFragment]: datagram send error !\n");
  else
  {
    m_fragments[num] = 1;
    if(num)
      m_fragmentsRemain--;

    // esli vse otoslali - zhdem nekotoroe vremya zaprosov pereslat' zanovo fragmenty,
    // i esli zaprosa ne prihodit unichtozhaemsya
    if(!m_fragmentsRemain)
    {
      m_selfDestroyTimer->setSingleShot(true);
      m_selfDestroyTimer->start(m_selfDestroyInterval);
    }
  }
}
//\*****************************************************************************
quint32 LargeDatagramOut::writeFragment(char* buf, quint32 num)
{
  if(!m_inited || !buf || num > m_totalFragments)
    return 0;

  uint fragmentSize = m_sizePerFragment;

  if(num == m_totalFragments && m_rest)
    if(!m_filename.isEmpty())
      fragmentSize = m_fileSize % m_sizePerFragment;
    else
      fragmentSize = m_dataSize % m_sizePerFragment;

  if(!num)
  {
    prepareInitHeader();
    memcpy(buf, m_header, m_headerSize);

    if(!m_filename.isEmpty())
    {
      QByteArray ba = m_filename.right(m_filename.size() - 1 - m_filename.lastIndexOf("/")).toUtf8();
      catUS2str(buf + m_headerSize, ba.size());
      memcpy(buf + m_headerSize + 2, ba.data(), ba.size());

      return m_headerSize + 2 + ba.size();
    }

    return m_headerSize;
  }

  AbstractChatCore::setParametrsLen(m_header, 0); // +65 ParametersLen / TotalSize      / == 0
  AbstractChatCore::setPacketNum   (m_header, num); //PacketNUM
  AbstractChatCore::setMessageLen  (m_header, m_sizePerFragment); // +61 MsgLen        / TotalFragments / DataLen
  memcpy(buf, m_header, m_headerSize);

  if(!m_filename.isEmpty())
  {
    m_file.seek((num - 1) * m_sizePerFragment);
    m_file.read(buf + m_headerSize, fragmentSize);

    return m_headerSize + fragmentSize;
  }

  memcpy(buf + m_headerSize, m_data + (m_sizePerFragment * (num - 1)), fragmentSize);

  return m_headerSize + fragmentSize;
}
//\*****************************************************************************
void LargeDatagramOut::selfDestroy()
{
  qDebug("[LargeDatagramOut[%d]::selfDestroy]", m_id);
  emit wantDie(this);
}
//\*****************************************************************************
void LargeDatagramOut::slot_confirmed(unsigned short ID)
{
  if(m_id == ID)
    m_confirmed = true;
}
//\*****************************************************************************
void LargeDatagramOut::fragmentsRequest(const char* dtgrm, quint16 len)
{
//   qDebug("[LargeDatagramOut::fragmentsRequest]");
  quint32 i;
  quint32 shift;
  quint32 pars_len = AbstractChatCore::parametrsLen(dtgrm);

  shift = AbstractChatCore::compNameLen(dtgrm) + AbstractChatCore::protocolLen() +
          AbstractChatCore::userNameLen(dtgrm) + AbstractChatCore::messageLen(dtgrm) + AbstractChatCore::optionsLen(dtgrm);

  if(len - shift < pars_len)
    return;

  QByteArray  pars(dtgrm + shift, pars_len);
  QByteArray  buf;
  quint16     frags_len;
  const char* frags;

  if(!m_inited)
    return;

  buf       = ChatCore::getParametr("FragmentsLen", pars);
  frags_len = str2US(buf.data());

  qDebug("[LargeDatagramOut::fragmentsRequest]: fragments_len = %d", frags_len);

  buf       = ChatCore::getParametr("FragmentsRequest", pars);
  frags     = buf.constData();

  qDebug("[LargeDatagramOut::fragmentsRequest]: fragmentsRemain before = %d", m_fragmentsRemain);

  for(i = 0; i < frags_len && i < m_totalFragments; i++)
    if(!frags[i])
    {
      m_fragmentsRemain += m_fragments[i + 1];
      m_fragments[i + 1] = 0;
    }

  qDebug("[LargeDatagramOut::fragmentsRequest]: fragmentsRemain after  = %d", m_fragmentsRemain);

  if(m_fragmentsRemain)
    m_selfDestroyTimer->stop();
}
//\*****************************************************************************
