/***************************************************************************
 *   Copyright (C) 2007 by Anistratov Oleg                                 *
 *   ower86@gmail.com                                                      *
 *                                                                         *
 *   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 "abstractchatcore.h"

#include "globals.h"

#include <assert.h>

#include "protocolversion3.h"
#include "protocolversion4.h"
#include "abstractprotocol.h"

#include <QDateTime>

AbstractProtocol* AbstractChatCore::m_protocol   = NULL;
ProtocolVersion3* AbstractChatCore::m_protocolV3 = new ProtocolVersion3;
ProtocolVersion4* AbstractChatCore::m_protocolV4 = new ProtocolVersion4;

AbstractChatCore::AbstractChatCore() :
  m_outputBuffer(NULL),
  m_outputBufferSize(65535),
  m_inputBuffer(NULL),
  m_inputBufferSize(65535),
  m_largeDtgrm(false),
  m_header(NULL),
  m_headerSize(0),
  m_data(NULL),
  m_dataSize(0),
  m_parametrs("")
{
  Hdr        = new QC_DatagramHeader;

  m_protocol = m_protocolV4;
//   m_protocol = m_protocolV3;

  m_outputBuffer = (char*)malloc(m_outputBufferSize);
  assert(NULL   != m_outputBuffer);
  m_inputBuffer  = (char*)malloc(m_inputBufferSize);
  assert(NULL   != m_inputBuffer );
}

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

  free(m_outputBuffer);

  // we do not need to free m_data because m_data = m_header + m_headerSize;
  free(m_header);
}

void AbstractChatCore::addParametr(const QString & name, const QByteArray & par, QByteArray & buf)
{
  char size[4];
  char nameSize[2];

  if(name.size() <= 65535)
  {
    catUS2str(nameSize, name.toUtf8().size());
    buf.append(nameSize[0]);
    buf.append(nameSize[1]);

    buf.append(name.toUtf8());

    catUL2str(size, par.size());
    buf.append(size[0]);
    buf.append(size[1]);
    buf.append(size[2]);
    buf.append(size[3]);

    buf.append(par);
  }
  else
    qWarning("[AbstractChatCore::addParametr]: '%s' is too large (%d bytes). not added\n", name.toLocal8Bit().data(), name.size());
}
//\*****************************************************************************
void AbstractChatCore::addParametr(const QString & name, const QByteArray & par)
{
  addParametr(name, par, parametrs());
}
//\*****************************************************************************
void AbstractChatCore::addParametr(const QString & name, const QString & filename)
{
  QFile file(filename);
  QByteArray par;
  char size[4];
  char nameSize[2];

//   qDebug("[ChatCore::addParametr]: name = %s", name.toLocal8Bit().data());

  if(name.size() <= 65535)
  {
    file.open(QIODevice::ReadOnly);

    if(file.exists ())
    {
      par = file.readAll();

      catUS2str(nameSize, name.toUtf8().size());
      parametrs().append(nameSize[0]);
      parametrs().append(nameSize[1]);

      parametrs().append(name.toUtf8());

      catUL2str(size, file.size());
      parametrs().append(size[0]);
      parametrs().append(size[1]);
      parametrs().append(size[2]);
      parametrs().append(size[3]);

      parametrs().append(par);

      par.clear();
    }
  }
  else
    qWarning("[AbstractChatCore::addParametr]: '%s' is too large (%d bytes). not added\n", name.toLocal8Bit().data(), name.size());

}
//\*****************************************************************************
void AbstractChatCore::clearParametrs()
{
  parametrs().clear();
  hdr()->parametrs.clear();
  hdr()->color = Qt::black;
}
//\*****************************************************************************
QByteArray AbstractChatCore::getParametr(const QString & par_name, const QByteArray& pars)
{
//   qDebug("[static ChatCore::getParametr]: req = '%s'", par_name.toLocal8Bit().data());

  qint32     idx = -1;
  quint32    size;
  quint32    pars_size = pars.size();
  QByteArray par("");
  uchar      n_size;
  char       arr2[2];
  char       par_size[4];
  QString    name;

  for(uint i = 0; i < pars_size; )
  {
    arr2[0] = pars.at(i++);
    arr2[1] = pars.at(i++);
    n_size  = str2US(arr2);

    name.clear();

    if(i + n_size >= pars_size)
      break;

    for(int j = 0; j < n_size; j++, i++)
      par.append(pars.at(i));
    name = QString().fromUtf8(par);

    par.resize(0);

//     qDebug("[static ChatCore::getParametr]: name = %s", name.toLocal8Bit().data());

    if(name == par_name)
    {
      idx = i - n_size;
      break;
    }

    if(i + 3 < pars_size)
    {
      par_size[0] = pars.at(i    );
      par_size[1] = pars.at(i + 1);
      par_size[2] = pars.at(i + 2);
      par_size[3] = pars.at(i + 3);

      size = str2UL(par_size);

      i += 4 + size;
    }

    else
      break;

  }

//   qDebug("[static ChatCore::getParametr]: pars.size() = %d", pars.size());
//   qDebug("[static ChatCore::getParametr]: idx = '%d'", idx);

  if(idx < 0)
    return QByteArray();
  idx += par_name.toUtf8().size();

  par_size[0] = pars.at(idx    );
  par_size[1] = pars.at(idx + 1);
  par_size[2] = pars.at(idx + 2);
  par_size[3] = pars.at(idx + 3);
  idx += 4;

  size = str2UL(par_size);

//   qDebug("[static ChatCore::getParametr]: size = %lu", (unsigned long)size);

  for(quint32 i = idx; i < idx + size && i < pars_size; i++)
    par.append(pars.at(i));

  return par;
}
//\*****************************************************************************
QByteArray AbstractChatCore::getParametr(const QString & par_name)
{
  return getParametr(par_name, parametrs());
}
//\*****************************************************************************
QColor AbstractChatCore::getColorParametr(QByteArray* pars)
{
  QByteArray par = getParametr("Color", *pars);
  QColor col(Qt::black);

  if(par.size() >= 3)
    col.setRgb((quint8)par[0], (quint8)par[1], (quint8)par[2]);
  else if(par.size() >= 4)
    col.setRgb(par[0], par[1], par[2], par[3]);

  return col;
}
//\*****************************************************************************
QMap<QString, QByteArray> AbstractChatCore::getAllParametrs(const QByteArray & pars)
{
  qint32     idx = -1;
  quint32    size;
  quint32    pars_size = pars.size();
  QByteArray par("");
  uchar      n_size;
  char       arr2[2];
  char       par_size[4];
  QString    name;

  QMap<QString, QByteArray> all_pars;

  for(uint i = 0; i < pars_size; )
  {
    arr2[0] = pars.at(i++);
    arr2[1] = pars.at(i++);
    n_size  = str2US(arr2);

    name.clear();

    if(i + n_size >= pars_size)
      break;

    for(int j = 0; j < n_size; j++, i++)
      par.append(pars.at(i));

    name = QString().fromUtf8(par);
    par.resize(0);

    //\*****************
    idx = i ;

    par_size[0] = pars.at(idx);
    par_size[1] = pars.at(idx + 1);
    par_size[2] = pars.at(idx + 2);
    par_size[3] = pars.at(idx + 3);
    idx += 4;

    size = str2UL(par_size);

    for(quint32 j = idx; j < idx + size && j < pars_size; j++)
      par.append(pars.at(j));

    all_pars[name] = par;
    par.resize(0);
    //\*****************

    if(i + 3 < pars_size)
    {
      par_size[0] = pars.at(i    );
      par_size[1] = pars.at(i + 1);
      par_size[2] = pars.at(i + 2);
      par_size[3] = pars.at(i + 3);

      size = str2UL(par_size);

      i += 4 + size;
    }

    else
      break;
  }

  return all_pars;
}

//\*****************************************************************************
void AbstractChatCore::prepareDatagram(AbstractChatCore::DataType dtgrm_type,
                                       quint64         dest_uid,
                                       const QString & nickname,
                                       const QString & compName,
                                       quint64         src_uid,
                                       const QString & msg       /* = 0*/,
                                       AbstractChatCore::ChannelType     chnnl_type/* = 0*/, // 0 - common, 1 - private, 2 - password
                                       unsigned long   dtgrm_id  /* = 0*/,
                                       unsigned long   dtgrm_num /* = 0*/)
{
  m_largeDtgrm = false;

  QByteArray msgData      = msg     .toUtf8();
  QByteArray nameData     = nickname.toUtf8();
  QByteArray compNameData = compName.toUtf8();

  int   unl    = nameData    .size(); // user_name_len
  int   cnl    = compNameData.size(); // comp_name_len
  char* buf;

  m_outputBufferSize = protocolLen() + unl + cnl + msgData.size() + m_parametrs.size() + optionsLen();

  if(m_outputBufferSize > MAX_PACKET_LEN)
  {
    m_largeDtgrm = true;
//     addParametr("Large");
  }

  qDebug("[AbstractChatCore::prepareDatagram]: pr_len = %d, unl = %d, cnl = %d, msgData.size() = %d, m_parametrs.size() = %d", AbstractChatCore::protocolLen(), unl, cnl, msgData.size(), m_parametrs.size());

  m_headerSize = protocolLen() + unl + cnl + optionsLen();
  // 4 + 4 - dlya razmerov esli paket budet fragmented
  m_dataSize     = msgData.size() + m_parametrs.size() + 4 + 4;
  m_header       = (char*)realloc(m_header, m_dataSize + m_headerSize);
  assert(NULL   != m_header);

  m_data = m_header + m_headerSize;

  setProgramId      (m_header);
  setProgramVersion (m_header);
  setProtocolVersion(m_header);
  setDestIp         (m_header, dest_uid);
  setSrcIp          (m_header, src_uid);
  setPacketType     (m_header, dtgrm_type);
  setPacketId       (m_header, dtgrm_id);
  setPacketNum      (m_header, dtgrm_num);
  setTime           (m_header, QDateTime::currentDateTime().toTime_t()/*time(NULL)*/); // FIXME why segfaults on time() ???
  setChannelType    (m_header, chnnl_type);
  setCompNameLen    (m_header, cnl);
  setUserNameLen    (m_header, unl);
  setMessageLen     (m_header, msgData.size());
  setParametrsLen   (m_header, m_parametrs.size());
  setOptionsLen     (m_header, 1); // FIXME !!!!!!
  setCompName       (m_header, compName);
  setUserName       (m_header, nickname);
  setCompressed     (m_header, false);

  buf = m_data;
  if(m_largeDtgrm)
  {
//     SetMsgLen      (m_header, msgData.size());
//     SetParametrsLen(m_header, m_parametrs.size());
    catUL2str(buf     , msgData  .size());
    catUL2str(buf += 4, m_parametrs.size());
    buf += 4;

  }

  memcpy(buf, msgData.data(), msgData.size());

  if(!m_parametrs.isEmpty())
     memcpy (buf += msgData.size(), m_parametrs.data(), m_parametrs.size());
     // NOTE setParametrs will NOT work!!
     // FIXME remove that hack
//      setParametrs(m_header, m_parametrs);

  if(!m_largeDtgrm)
  {
    memcpy(m_outputBuffer               , m_header, m_headerSize  );
    memcpy(m_outputBuffer + m_headerSize, m_data  , m_dataSize - 8); // -8, t.k. paket ne fragmented
    m_outputBufferSize = m_headerSize + m_dataSize - 8;
  }
}

//\*****************************************************************************
bool AbstractChatCore::fillHeader()
{
  QHostAddress address;

  AbstractProtocol* prot = m_protocol;

  setProtocolVersion(checkProtocolVersion(m_inputBuffer));

  // Razbor Dannyh
  if(m_inputBufferSize < protocolLen())
  {
    qWarning("[AbstractChatCore::fillHeader]: [LengthError!] (m_inputBufferSize < AbstractChatCore::protocolLen()). Ignoring this packet\n");
    m_protocol = prot;
    return 0;
  }
  if(strncmp(m_inputBuffer, programId(), programIdLen()))
  {
    qWarning("[AbstractChatCore::fillHeader]: [ID_ERROR!] (strncmp(m_inputBuffer, AbstractChatCore::programId(), 18) != 0). Ignoring this packet\n");
    m_protocol = prot;
    return 0;
  }

  Hdr->receive_tm      = QDateTime::currentDateTime().toTime_t();//time(NULL);
  Hdr->programVersion  = programVersion(m_inputBuffer);

  if(Hdr->programVersion < 5)
  {
    qWarning("[AbstractChatCore::fillHeader]: [error] packet from old version of qchat ( < 5). Ignoring this packet\n");
    m_protocol = prot;
    return 0;
  }

  Hdr->protocolVersion = protocolVersion(m_inputBuffer);

  if(Hdr->protocolVersion != protocolVersion())
  {
    qWarning("[AbstractChatCore::fillHeader]: [error] protocol version mismatch (%d != %d). Ignoring this packet\n", Hdr->protocolVersion, protocolVersion());
    m_protocol = prot;
    return 0;
  }

  Hdr->dest_ip         = destIp      (m_inputBuffer);
  Hdr->src_ip          = srcIp       (m_inputBuffer);
  Hdr->type            = packetType  (m_inputBuffer);
  Hdr->id              = packetId    (m_inputBuffer);
  Hdr->num             = packetNum   (m_inputBuffer);
  Hdr->tm              = time        (m_inputBuffer);
  Hdr->chnnl_id        = channelType (m_inputBuffer);
  Hdr->comp_name_len   = compNameLen (m_inputBuffer);
  Hdr->name_len        = userNameLen (m_inputBuffer);
  Hdr->msg_len         = messageLen  (m_inputBuffer);
  Hdr->parametrs_len   = parametrsLen(m_inputBuffer);

  qDebug("[AbstractChatCore::fillHeader]: type = %d", Hdr->type);

  uint expectedSize;

  if(protocolVersion() < 4)
    expectedSize = protocolLen() + Hdr->comp_name_len + Hdr->name_len + Hdr->msg_len + Hdr->parametrs_len;
  else
    expectedSize = packetSize(m_inputBuffer);

  if(m_inputBufferSize < expectedSize)
  {
    qWarning("[AbstractChatCore::fillHeader]: [error] wrong length of packet (actual size(%d) smaller than expected(%u)). Ignoring this packet\n", m_inputBufferSize, expectedSize);

    qDebug("[AbstractChatCore::fillHeader]: AbstractChatCore::protocolLen() = %d, Hdr->comp_name_len = %d, Hdr->name_len = %d, Hdr->msg_len = %lu, Hdr->parametrs_len = %lu", protocolLen(), Hdr->comp_name_len, Hdr->name_len, Hdr->msg_len, Hdr->parametrs_len);

    m_protocol = prot;
    return 0;
  }

  Hdr->comp_name = compName(m_inputBuffer, m_inputBufferSize);
  Hdr->name      = userName(m_inputBuffer, m_inputBufferSize);
  Hdr->msg       = message (m_inputBuffer, m_inputBufferSize);

  Hdr->parametrs.clear();
  Hdr->parametrs = parametrs(m_inputBuffer, m_inputBufferSize);
  Hdr->color = getColorParametr(&Hdr->parametrs);

  Hdr->isHtml = !getParametr("HTML", Hdr->parametrs).isNull();

  //********* getting versionName *********
  QByteArray ba = 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 = getParametr("Status", Hdr->parametrs);
  if(!ba.isEmpty())
    Hdr->status = ba[0];

  m_protocol = prot;
  return 1;
}
//\*****************************************************************************

void AbstractChatCore::newHdr()
{
  Hdr = new QC_DatagramHeader;
}

bool isSystemMsg(quint16 type)
{
  switch(type)
  {
    case AbstractChatCore::CONNECTED :
    case AbstractChatCore::DISCONNECTED :
    case AbstractChatCore::INF_STATUS_CHNGD :
      return true;
  }

  return false;
}

char* AbstractChatCore::uncompress(const char* buf, uint& size)
{
  char* data_pos;
  uint  shift = protocolLen() + optionsLen(buf) + compNameLen(buf) + userNameLen(buf);
  char* data;
  QByteArray ba;

  if(size < protocolLen() || size < shift)
    return NULL;

  data_pos = (char*)buf + shift;

  ba = qUncompress((const uchar*)data_pos, size - shift);

  size = ba.size() + shift;

  data = (char*)malloc(size);

  memcpy(data        , buf      , shift);
  memcpy(data + shift, ba.data(), ba.size());

  return data;
}

char* AbstractChatCore::compress(const char * buf, uint & size)
{
  char* data_pos;
  uint  shift = protocolLen() + optionsLen(buf) + compNameLen(buf) + userNameLen(buf);
  char* data;
  QByteArray ba;

  if(size < protocolLen() || size < shift)
    return NULL;

  data_pos = (char*)buf + shift;

  ba = qCompress((const uchar*)data_pos, size - shift, 9);

  size = ba.size() + shift;

  data = (char*)malloc(size);

  memcpy(data        , buf      , shift);
  memcpy(data + shift, ba.data(), ba.size());

  return data;
}

void AbstractChatCore::setProtocolVersion(uint ver)
{
  if     (ver == 3) m_protocol = m_protocolV3;
  else if(ver == 4) m_protocol = m_protocolV4;
}

uint AbstractChatCore::checkProtocolVersion(const char * buf)
{
  AbstractProtocol* tmp = m_protocol;
//   int res;

  m_protocol = m_protocolV3;

  if(!strncmp(buf, programId(), programIdLen()) && protocolVersion(buf) == 3)
  {
    m_protocol = tmp;
    return 3;
  }

  m_protocol = m_protocolV4;

  if(!strncmp(buf, programId(), programIdLen()) && protocolVersion(buf) == 4)
  {
    m_protocol = tmp;
    return 4;
  }

//   res = protocolVersion(buf);

  m_protocol = tmp;

  return 0;
}
