/****************************************************************************
**
** https://www.qxorm.com/
** Copyright (C) 2013 XDL Team (ic-east.com)
**
** This file is part of the QxOrm library
**
** This software is provided 'as-is', without any express or implied
** warranty. In no event will the authors be held liable for any
** damages arising from the use of this software
**
** Commercial Usage
** Licensees holding valid commercial QxOrm licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and XDL Team
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file 'license.gpl3.txt' included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met : http://www.gnu.org/copyleft/gpl.html
**
** If you are unsure which license is appropriate for your use, or
** if you have questions regarding the use of this file, please contact :
** ic-east.com
**
****************************************************************************/

#ifdef _QX_ENABLE_QT_NETWORK

#include <QxPrecompiled.h>

#include <QxService/QxTools.h>
#include <QxService/QxConnect.h>

#include <QxCommon/QxSimpleCrypt.h>
#include <QxCommon/QxExceptionCode.h>

#include <QxSerialize/QxSerializeQDataStream.h>
#include <QxSerialize/QxSerializeQJson.h>
#include <QxSerialize/QJson/QxSerializeQJson_QxTransaction.h>

#include <QxMemLeak/mem_leak.h>

#define QX_SERVICE_TOOLS_HEADER_SIZE (sizeof(quint32) + sizeof(quint16) + sizeof(quint16) + sizeof(quint16)) // (serialized data size) + (serialization type) + (compress data) + (encrypt data)
#define QX_SERVICE_MIN_SIZE_TO_COMPRESS_DATA 2000

namespace qx
{
      namespace service
      {

            qx_bool QxTools::readSocket(QTcpSocket &socket, QxTransaction &transaction, quint32 &size)
            {
                  while (socket.bytesAvailable() < (qint64)(QX_SERVICE_TOOLS_HEADER_SIZE))
                  {
                        if (!socket.waitForReadyRead(QxConnect::getSingleton()->getMaxWait()))
                        {
                              return qx_bool(QX_ERROR_SERVICE_READ_ERROR, "invalid bytes count available to retrieve transaction header (" + socket.errorString() + ")");
                        }
                  }

                  quint32 uiSerializedSize = 0;
                  quint16 uiSerializationType(0), uiCompressData(0), uiEncryptData(0);
                  QByteArray dataHeader = socket.read((qint64)(QX_SERVICE_TOOLS_HEADER_SIZE));
                  qAssert(dataHeader.size() == (int)(QX_SERVICE_TOOLS_HEADER_SIZE));
                  QDataStream in(&dataHeader, QIODevice::ReadOnly);
                  in.setVersion(QDataStream::Qt_4_5);
                  in >> uiSerializedSize;
                  in >> uiSerializationType;
                  in >> uiCompressData;
                  in >> uiEncryptData;

                  while (socket.bytesAvailable() < (qint64)(uiSerializedSize))
                  {
                        if (!socket.waitForReadyRead(QxConnect::getSingleton()->getMaxWait()))
                        {
                              return qx_bool(QX_ERROR_SERVICE_READ_ERROR, "invalid bytes count available to retrieve transaction serialized data (" + socket.errorString() + ")");
                        }
                  }

                  QByteArray dataSerialized = socket.read((qint64)(uiSerializedSize));
                  qAssert(dataSerialized.size() == (int)(uiSerializedSize));
                  size = (quint32)(QX_SERVICE_TOOLS_HEADER_SIZE + uiSerializedSize);

                  if (uiEncryptData != 0)
                  {
                        QxSimpleCrypt crypto(QxConnect::getSingleton()->getEncryptKey());
                        QByteArray decrypted = crypto.decryptToByteArray(dataSerialized);
                        if ((crypto.lastError() != QxSimpleCrypt::ErrorNoError) || decrypted.isEmpty())
                        {
                              return qx_bool(QX_ERROR_UNKNOWN, "an error occured during decryption of data");
                        }
                        dataSerialized = decrypted;
                  }

                  if (uiCompressData != 0)
                  {
                        QByteArray uncompressed = qUncompress(dataSerialized);
                        if (!uncompressed.isEmpty())
                        {
                              dataSerialized = uncompressed;
                        }
                  }

                  qx_bool bDeserializeOk;
                  switch (static_cast<QxConnect::serialization_type>(uiSerializationType))
                  {
#if _QX_SERIALIZE_BINARY
                  case QxConnect::serialization_binary:
                        bDeserializeOk = qx::serialization::binary::from_byte_array(transaction, dataSerialized);
                        break;
#endif // _QX_SERIALIZE_BINARY
#if _QX_SERIALIZE_XML
                  case QxConnect::serialization_xml:
                        bDeserializeOk = qx::serialization::xml::from_byte_array(transaction, dataSerialized);
                        break;
#endif // _QX_SERIALIZE_XML
#if _QX_SERIALIZE_TEXT
                  case QxConnect::serialization_text:
                        bDeserializeOk = qx::serialization::text::from_byte_array(transaction, dataSerialized);
                        break;
#endif // _QX_SERIALIZE_TEXT
#if _QX_SERIALIZE_PORTABLE_BINARY
                  case QxConnect::serialization_portable_binary:
                        bDeserializeOk = qx::serialization::portable_binary::from_byte_array(transaction, dataSerialized, 0);
                        break;
#endif // _QX_SERIALIZE_PORTABLE_BINARY
#if _QX_SERIALIZE_WIDE_BINARY
                  case QxConnect::serialization_wide_binary:
                        bDeserializeOk = qx::serialization::wide::binary::from_byte_array(transaction, dataSerialized);
                        break;
#endif // _QX_SERIALIZE_WIDE_BINARY
#if _QX_SERIALIZE_WIDE_XML
                  case QxConnect::serialization_wide_xml:
                        bDeserializeOk = qx::serialization::wide::xml::from_byte_array(transaction, dataSerialized);
                        break;
#endif // _QX_SERIALIZE_WIDE_XML
#if _QX_SERIALIZE_WIDE_TEXT
                  case QxConnect::serialization_wide_text:
                        bDeserializeOk = qx::serialization::wide::text::from_byte_array(transaction, dataSerialized);
                        break;
#endif // _QX_SERIALIZE_WIDE_TEXT
#if _QX_SERIALIZE_POLYMORPHIC
                  case QxConnect::serialization_polymorphic_binary:
                        bDeserializeOk = qx::serialization::polymorphic_binary::from_byte_array(transaction, dataSerialized);
                        break;
                  case QxConnect::serialization_polymorphic_xml:
                        bDeserializeOk = qx::serialization::polymorphic_xml::from_byte_array(transaction, dataSerialized);
                        break;
                  case QxConnect::serialization_polymorphic_text:
                        bDeserializeOk = qx::serialization::polymorphic_text::from_byte_array(transaction, dataSerialized);
                        break;
#endif // _QX_SERIALIZE_POLYMORPHIC
                  case QxConnect::serialization_qt:
                        bDeserializeOk = qx::serialization::qt::from_byte_array(transaction, dataSerialized);
                        break;
#ifndef _QX_NO_JSON
                  case QxConnect::serialization_json:
                        bDeserializeOk = qx::serialization::json::from_byte_array(transaction, dataSerialized);
                        break;
#endif // _QX_NO_JSON
                  default:
                        return qx_bool(QX_ERROR_UNKNOWN, "unknown serialization type to read data from socket");
                  }

                  return bDeserializeOk;
            }

            qx_bool QxTools::writeSocket(QTcpSocket &socket, QxTransaction &transaction, quint32 &size)
            {
                  QByteArray dataSerialized;
                  std::string owner;
                  Q_UNUSED(owner);
                  std::wstring w_owner;
                  Q_UNUSED(w_owner);
                  switch (QxConnect::getSingleton()->getSerializationType())
                  {
#if _QX_SERIALIZE_BINARY
                  case QxConnect::serialization_binary:
                        dataSerialized = qx::serialization::binary::to_byte_array(transaction, (&owner));
                        break;
#endif // _QX_SERIALIZE_BINARY
#if _QX_SERIALIZE_XML
                  case QxConnect::serialization_xml:
                        dataSerialized = qx::serialization::xml::to_byte_array(transaction, (&owner));
                        break;
#endif // _QX_SERIALIZE_XML
#if _QX_SERIALIZE_TEXT
                  case QxConnect::serialization_text:
                        dataSerialized = qx::serialization::text::to_byte_array(transaction, (&owner));
                        break;
#endif // _QX_SERIALIZE_TEXT
#if _QX_SERIALIZE_PORTABLE_BINARY
                  case QxConnect::serialization_portable_binary:
                        dataSerialized = qx::serialization::portable_binary::to_byte_array(transaction, (&owner), 0);
                        break;
#endif // _QX_SERIALIZE_PORTABLE_BINARY
#if _QX_SERIALIZE_WIDE_BINARY
                  case QxConnect::serialization_wide_binary:
                        dataSerialized = qx::serialization::wide::binary::to_byte_array(transaction, (&w_owner));
                        break;
#endif // _QX_SERIALIZE_WIDE_BINARY
#if _QX_SERIALIZE_WIDE_XML
                  case QxConnect::serialization_wide_xml:
                        dataSerialized = qx::serialization::wide::xml::to_byte_array(transaction, (&w_owner));
                        break;
#endif // _QX_SERIALIZE_WIDE_XML
#if _QX_SERIALIZE_WIDE_TEXT
                  case QxConnect::serialization_wide_text:
                        dataSerialized = qx::serialization::wide::text::to_byte_array(transaction, (&w_owner));
                        break;
#endif // _QX_SERIALIZE_WIDE_TEXT
#if _QX_SERIALIZE_POLYMORPHIC
                  case QxConnect::serialization_polymorphic_binary:
                        dataSerialized = qx::serialization::polymorphic_binary::to_byte_array(transaction, (&owner));
                        break;
                  case QxConnect::serialization_polymorphic_xml:
                        dataSerialized = qx::serialization::polymorphic_xml::to_byte_array(transaction, (&owner));
                        break;
                  case QxConnect::serialization_polymorphic_text:
                        dataSerialized = qx::serialization::polymorphic_text::to_byte_array(transaction, (&owner));
                        break;
#endif // _QX_SERIALIZE_POLYMORPHIC
                  case QxConnect::serialization_qt:
                        dataSerialized = qx::serialization::qt::to_byte_array(transaction, (&owner));
                        break;
#ifndef _QX_NO_JSON
                  case QxConnect::serialization_json:
                        dataSerialized = qx::serialization::json::to_byte_array(transaction, (&owner));
                        break;
#endif // _QX_NO_JSON
                  default:
                        return qx_bool(QX_ERROR_UNKNOWN, "unknown serialization type to write data to socket");
                  }

                  if (dataSerialized.isEmpty())
                  {
                        return qx_bool(QX_ERROR_UNKNOWN, "an error occured during serialization of data");
                  }

                  quint16 uiCompressData = 0;
                  if (QxConnect::getSingleton()->getCompressData() && (dataSerialized.size() > QX_SERVICE_MIN_SIZE_TO_COMPRESS_DATA))
                  {
                        QByteArray compressed = qCompress(dataSerialized, -1);
                        if (!compressed.isEmpty())
                        {
                              dataSerialized = compressed;
                              uiCompressData = 1;
                        }
                  }

                  quint16 uiEncryptData = 0;
                  if (QxConnect::getSingleton()->getEncryptData())
                  {
                        QxSimpleCrypt crypto(QxConnect::getSingleton()->getEncryptKey());
                        crypto.setCompressionMode(QxSimpleCrypt::CompressionNever);
                        crypto.setIntegrityProtectionMode(QxSimpleCrypt::ProtectionChecksum);
                        QByteArray encrypted = crypto.encryptToByteArray(dataSerialized);
                        if ((crypto.lastError() != QxSimpleCrypt::ErrorNoError) || encrypted.isEmpty())
                        {
                              return qx_bool(QX_ERROR_UNKNOWN, "an error occured during encryption of data");
                        }
                        dataSerialized = encrypted;
                        uiEncryptData = 1;
                  }

                  QByteArray dataHeader;
                  QDataStream out(&dataHeader, QIODevice::WriteOnly);
                  out.setVersion(QDataStream::Qt_4_5);
                  out << (quint32)(dataSerialized.size());
                  out << (quint16)(QxConnect::getSingleton()->getSerializationType());
                  out << (quint16)(uiCompressData);
                  out << (quint16)(uiEncryptData);
                  qAssert(dataHeader.size() == (int)(QX_SERVICE_TOOLS_HEADER_SIZE));

                  qint64 iTotalWritten = 0;
                  qint64 iTotalToWrite = (qint64)(dataHeader.size());
                  const char *pDataHeader = dataHeader.constData();
                  while (iTotalWritten < iTotalToWrite)
                  {
                        qint64 iWritten = socket.write((pDataHeader + iTotalWritten), (iTotalToWrite - iTotalWritten));
                        if (iWritten == -1)
                        {
                              break;
                        }
                        iTotalWritten += iWritten;
                  }

                  if (iTotalWritten != iTotalToWrite)
                  {
                        return qx_bool(QX_ERROR_SERVICE_WRITE_ERROR, "unable to write all data bytes (header) to socket (" + socket.errorString() + ")");
                  }

                  iTotalWritten = 0;
                  iTotalToWrite = (qint64)(dataSerialized.size());
                  const char *pDataSerialized = dataSerialized.constData();
                  while (iTotalWritten < iTotalToWrite)
                  {
                        qint64 iWritten = socket.write((pDataSerialized + iTotalWritten), (iTotalToWrite - iTotalWritten));
                        if (iWritten == -1)
                        {
                              break;
                        }
                        iTotalWritten += iWritten;
                  }

                  size = (quint32)(dataHeader.size() + dataSerialized.size());
                  return ((iTotalWritten == iTotalToWrite) ? qx_bool(true) : qx_bool(QX_ERROR_SERVICE_WRITE_ERROR, "unable to write all data bytes (serialized data) to socket (" + socket.errorString() + ")"));
            }

      } // namespace service
} // namespace qx

#endif // _QX_ENABLE_QT_NETWORK
