/*
    This file is part of the KDE games library
    Copyright (C) 2001 Burkhard Lehner (Burkhard.Lehner@gmx.de)

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License version 2 as published by the Free Software Foundation.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.
*/

#include <kdebug.h>
#include <stdio.h>

#include <qbuffer.h>
#include <qtimer.h>

#include "kmessageio.h"
#include "kmessageserver.h"

#include "kmessageclient.h"

class KMessageClientPrivate
{
public:
  KMessageClientPrivate ()
    : adminID (0), connection (0)
  {}

  ~KMessageClientPrivate ()
  {
    delete connection;
  }

  Q_UINT32 adminID;
  QValueList <Q_UINT32> clientList;
  KMessageIO *connection;

  bool isLocked;
  QValueList <QByteArray> delayedMessages;
};

KMessageClient::KMessageClient (QObject *parent, const char *name)
  : QObject (parent, name)
{
  d = new KMessageClientPrivate ();
  d->isLocked = false;
}

KMessageClient::~KMessageClient ()
{
  d->delayedMessages.clear();
  delete d;
}

// -- setServer stuff

void KMessageClient::setServer (const QString &host, Q_UINT16 port)
{
  setServer (new KMessageSocket (host, port));
}

void KMessageClient::setServer (KMessageServer *server)
{
  KMessageDirect *serverIO = new KMessageDirect ();
  setServer (new KMessageDirect (serverIO));
  server->addClient (serverIO);
}

void KMessageClient::setServer (KMessageIO *connection)
{
  if (d->connection)
  {
    delete d->connection;
    kdDebug (11001) << k_funcinfo << ": We are changing the server!" << endl;
  }

  d->connection = connection;
  if (connection )
  {
    connect (connection, SIGNAL (received(const QByteArray &)),
             this, SLOT (processIncomingMessage(const QByteArray &)));
    connect (connection, SIGNAL (connectionBroken()),
             this, SLOT (removeBrokenConnection ()));
  }
}

// -- id stuff

Q_UINT32 KMessageClient::id () const
{
  return (d->connection) ? d->connection->id () : 0;
}

bool KMessageClient::isAdmin () const
{
  return id() != 0 && id() == adminId();
}

Q_UINT32 KMessageClient::adminId () const
{
  return d->adminID;
}

const QValueList <Q_UINT32> &KMessageClient::clientList() const
{
  return d->clientList;
}

bool KMessageClient::isConnected () const
{
  return d->connection && d->connection->isConnected();
}

bool KMessageClient::isNetwork () const
{
  return isConnected() ? d->connection->isNetwork() : false;
}

Q_UINT16 KMessageClient::peerPort () const
{
 return d->connection ? d->connection->peerPort() : 0;
}

QString KMessageClient::peerName () const
{
 return d->connection ? d->connection->peerName() : QString::fromLatin1("localhost");
}

// --------------------- Sending messages

void KMessageClient::sendServerMessage (const QByteArray &msg)
{
  if (!d->connection)
  {
    kdWarning (11001) << k_funcinfo << ": We have no connection yet!" << endl;
    return;
  }
  d->connection->send (msg);
}

void KMessageClient::sendBroadcast (const QByteArray &msg)
{
  QByteArray sendBuffer;
  QBuffer buffer (sendBuffer);
  buffer.open (IO_WriteOnly);
  QDataStream stream (&buffer);

  stream << static_cast<Q_UINT32> ( KMessageServer::REQ_BROADCAST );
  buffer.QIODevice::writeBlock (msg);
  sendServerMessage (sendBuffer);
}

void KMessageClient::sendForward (const QByteArray &msg, const QValueList <Q_UINT32> &clients)
{
  QByteArray sendBuffer;
  QBuffer buffer (sendBuffer);
  buffer.open (IO_WriteOnly);
  QDataStream stream (&buffer);

  stream << static_cast<Q_UINT32>( KMessageServer::REQ_FORWARD ) << clients;
  buffer.QIODevice::writeBlock (msg);
  sendServerMessage (sendBuffer);
}

void KMessageClient::sendForward (const QByteArray &msg, Q_UINT32 client)
{
  sendForward (msg, QValueList <Q_UINT32> () << client);
}


// --------------------- Receiving and processing messages

void KMessageClient::processIncomingMessage (const QByteArray &msg)
{
  if (d->isLocked)
  {
    d->delayedMessages.append(msg);
    return;
  }
  if (d->delayedMessages.count() > 0)
  {
    d->delayedMessages.append (msg);
    QByteArray first = d->delayedMessages.front();
    d->delayedMessages.pop_front();
    processMessage (first);
  }
  else
  {
    processMessage(msg);
  }
}

void KMessageClient::processMessage (const QByteArray &msg)
{
  if (d->isLocked)
  { // must NOT happen, since we check in processIncomingMessage as well as in processFirstMessage
    d->delayedMessages.append(msg);
    return;
  }
  QBuffer in_buffer (msg);
  in_buffer.open (IO_ReadOnly);
  QDataStream in_stream (&in_buffer);


  bool unknown = false;

  Q_UINT32 messageID;
  in_stream >> messageID;
  switch (messageID)
  {
    case KMessageServer::MSG_BROADCAST:
      {
        Q_UINT32 clientID;
        in_stream >> clientID;
        emit broadcastReceived (in_buffer.readAll(), clientID);
      }
      break;

    case KMessageServer::MSG_FORWARD:
      {
        Q_UINT32 clientID;
        QValueList <Q_UINT32> receivers;
        in_stream >> clientID >> receivers;
        emit forwardReceived (in_buffer.readAll(), clientID, receivers);
      }
      break;

    case KMessageServer::ANS_CLIENT_ID:
      {
        bool old_admin = isAdmin();
        Q_UINT32 clientID;
        in_stream >> clientID;
        d->connection->setId (clientID);
        if (old_admin != isAdmin())
          emit adminStatusChanged (isAdmin());
      }
      break;

    case KMessageServer::ANS_ADMIN_ID:
      {
        bool old_admin = isAdmin();
        in_stream >> d->adminID;
        if (old_admin != isAdmin())
          emit adminStatusChanged (isAdmin());
      }
      break;

    case KMessageServer::ANS_CLIENT_LIST:
      {
        in_stream >> d->clientList;
      }
      break;

    case KMessageServer::EVNT_CLIENT_CONNECTED:
      {
        Q_UINT32 id;
        in_stream >> id;

        if (d->clientList.contains (id))
          kdWarning (11001) << k_funcinfo << ": Adding a client that already existed!" << endl;
        else
          d->clientList.append (id);

        emit eventClientConnected (id);
      }
      break;

    case KMessageServer::EVNT_CLIENT_DISCONNECTED:
      {
        Q_UINT32 id;
        Q_INT8 broken;
        in_stream >> id >> broken;

        if (!d->clientList.contains (id))
          kdWarning (11001) << k_funcinfo << ": Removing a client that doesn't exist!" << endl;
        else
          d->clientList.remove (id);

        emit eventClientDisconnected (id, bool (broken));
      }
      break;

    default:
      unknown = true;
  }

  if (!unknown && !in_buffer.atEnd())
    kdWarning (11001) << k_funcinfo << ": Extra data received for message ID " << messageID << endl;

  emit serverMessageReceived (msg, unknown);

  if (unknown)
    kdWarning (11001) << k_funcinfo << ": received unknown message ID " << messageID << endl;
}

void KMessageClient::processFirstMessage()
{
  if (d->isLocked)
  {
    return;
  }
  if (d->delayedMessages.count() == 0)
  {
    kdDebug(11001) << k_funcinfo << ": no messages delayed" << endl;
    return;
  }
  QByteArray first = d->delayedMessages.front();
  d->delayedMessages.pop_front();
  processMessage (first);
}

void KMessageClient::removeBrokenConnection ()
{
  kdDebug (11001) << k_funcinfo << ": timer single shot for removeBrokenConnection"<<this << endl;
  // MH We cannot directly delete the socket. otherwise QSocket crashes
  QTimer::singleShot( 0, this, SLOT(removeBrokenConnection2()) );
  return;
}


void KMessageClient::removeBrokenConnection2 ()
{
  kdDebug (11001) << k_funcinfo << ": Broken:Deleting the connection object"<<this << endl;

  emit aboutToDisconnect(id());
  delete d->connection;
  d->connection = 0;
  d->adminID = 0;
  emit connectionBroken();
  kdDebug (11001) << k_funcinfo << ": Broken:Deleting the connection object DONE" << endl;
}

void KMessageClient::disconnect ()
{
  kdDebug (11001) << k_funcinfo << ": Disconnect:Deleting the connection object" << endl;

  emit aboutToDisconnect(id());
  delete d->connection;
  d->connection = 0;
  d->adminID = 0;
  emit connectionBroken();
  kdDebug (11001) << k_funcinfo << ": Disconnect:Deleting the connection object DONE" << endl;
}

void KMessageClient::lock ()
{
  d->isLocked = true;
}

void KMessageClient::unlock ()
{
  d->isLocked = false;
  for (unsigned int i = 0; i < d->delayedMessages.count(); i++)
  {
    QTimer::singleShot(0, this, SLOT(processFirstMessage()));
  }
}

unsigned int KMessageClient::delayedMessageCount() const
{
  return d->delayedMessages.count();
}

#include "kmessageclient.moc"
