/*****************************************************************************
 ** Class QRender::Client
 **
 ** 
 **   Created : Tue Nov 16 12:09:08 2004
 **        by : Varol Okan using kate editor
 ** Copyright : (c) Varol Okan
 ** License   : GPL v 2.0
 **
 ** This class handles the client side and is Qt 3.x code so that it can be 
 ** used from within QDVDAuthor.
 **
 ** The Manager is there to simplify handle multiple rendering of DVDMenus.
 **
 ** Th Client is a separate thread which handles the incoming messages from
 ** the rendering server.
 ** There are three different types of messages.
 ** 
 ** 1) The status/communication messages
 ** 2) The Exception messages
 ** 3) The terminate message
 **
 ** All message IDs are defined in qrender/shared.h
 ** The basic message structure is as simple as can be.
 ** 2 Bytes   : Message ID
 ** 8 Bytes   : Message Length
 ** remainder : Message body ( Text, Image, or Final video )
 **
 *****************************************************************************/

#include <math.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/stat.h>

#include <qdir.h>
#include <qfile.h>
#include <qimage.h>
#include <qtimer.h>
#include <qobject.h>
#include <qbuffer.h>
#include <qpainter.h>
#include <qapplication.h>
#include <qsocketdevice.h>

#include "render_client.h"

#include "global.h"
#include "messagebox.h"
#include "qdvdauthor.h"
#include "buttonobject.h"
#include "xml_slideshow.h"
#include "sourcefileentry.h"
#include "../qrender/shared.h"

#ifdef debug_out
#undef debug_out
#endif
#if 1
static void dummy(const char*,...){};
#define debug_out dummy
#define sdebug_out dummy
#else
//#define debug_out printf ( "%s::%s::%d >", __FILE__, __FUNCTION__, __LINE__ ); printf
#define debug_out printf ( "thr:%X obj:%p :: %s::%s::%d >", (uint)pthread_self(), this, __FILE__, __FUNCTION__, __LINE__ ); printf
#define sdebug_out printf ( "thr:%X obj:          :: %s::%s::%d >", (uint)pthread_self(), __FILE__, __FUNCTION__, __LINE__ ); printf
#endif

namespace Render
{

// Init the one and only Manager
Manager *Manager::m_pSelf = NULL;

Manager::ServerInfo::ServerInfo ( unsigned long iServerIP, unsigned int iPort, unsigned int iConcurrentThreads )
{
  m_iServerIP          = iServerIP;
  m_iServerPort        = iPort;
  m_iConcurrentThreads = iConcurrentThreads;
  m_bRecoveryMode      = false;
  m_bStartingServer    = false;
}

Manager::ServerInfo::~ServerInfo ( )
{
  Client *pClient = NULL;
  QValueList<Client *>::iterator it = m_listOfClients.begin ( );
  while ( it != m_listOfClients.end ( ) )  {
    pClient = *it++;
//  pClient->closeConnection ( );
    if ( pClient->running    ( ) )
         pClient->terminate  ( );
    delete pClient;
  }
  m_listOfClients.clear ( );
}

unsigned long Manager::ServerInfo::getIP ( )
{
  return m_iServerIP;
}

unsigned int Manager::ServerInfo::getPort ( )
{
  return m_iServerPort;
}

void Manager::ServerInfo::setRecovery ( bool bRecovery )
{
  m_bRecoveryMode = bRecovery;
}

bool Manager::ServerInfo::inRecovery ( )
{
  return m_bRecoveryMode;
}

bool Manager::ServerInfo::startingServer ( )
{
  return m_bStartingServer;
}

void Manager::ServerInfo::setStarting ( bool bStarting )
{
  m_bStartingServer = bStarting;
}

void Manager::ServerInfo::startServer ( )
{
  if ( startingServer ( ) )
    return;
  // We need to block other threads from trying the same to give qrender time to get started
  setStarting ( true );

  Utils theUtils;
  QString qsCommand = theUtils.getToolPath ( "qrender" );
  if ( qsCommand.isEmpty ( ) )  {
    qsCommand = Global::qsSystemPath + "/bin/qrender";
    QFileInfo fileInfo ( qsCommand );
    if ( ! fileInfo.exists ( ) )  {
      qsCommand = theUtils.checkForExe ( "qrender" );
      if ( qsCommand.isEmpty ( ) )
           qsCommand = "qrender";
    }
  }

  qsCommand += " -v 5";
  int iRet = system ( qsCommand.ascii ( ) );
  iRet = iRet; // quiet down the compiler.
  debug_out ( "ret<%d> = <%s>\n", iRet, qsCommand.ascii ( ) );
}

bool Manager::ServerInfo::hasClient ( Client *pClient )
{
  QValueList<Client *>::iterator it = m_listOfClients.begin ( );
  while ( it != m_listOfClients.end ( ) )  {
    if ( *it++ == pClient )
      return true;
  }
  return false;
}

Client *Manager::ServerInfo::addClient ( SourceFileEntry *pEntry )
{
  if ( ! pEntry )
    return NULL;

  Client *pClient = new Client ( pEntry  );
  m_listOfClients.append       ( pClient );

  return pClient;
}

bool Manager::ServerInfo::removeClient ( SourceFileEntry *pEntry )
{
  if ( ! pEntry )
    return false;

  Client *pClient = NULL;
  QValueList<Client *>::iterator it = m_listOfClients.begin ( );
  while ( it != m_listOfClients.end ( ) )  {
    pClient = *it++;
    if ( pClient->sourceFileEntry ( ) == pEntry )  {
      m_listOfClients.remove      ( pClient );
      pClient->killServerThread   ( );

      delete pClient;
      startNextClient ( );
      return true;
    }
  }
  return false;
}

Client *Manager::ServerInfo::addClient ( ButtonTransition *pTrans )
{
  if ( ! pTrans )
    return NULL;

  Client *pClient = new Client ( pTrans  );
  m_listOfClients.append       ( pClient );

  return pClient;
}

bool Manager::ServerInfo::removeClient ( ButtonTransition *pTrans )
{
  if ( ! pTrans )
    return false;

  Client *pClient = NULL;
  QValueList<Client *>::iterator it = m_listOfClients.begin ( );
  while ( it != m_listOfClients.end ( ) )  {
    pClient = *it++;
    if ( *pClient->buttonTransition ( ) == *pTrans )  {
      m_listOfClients.remove        ( pClient );
      pClient->killServerThread     ( );

      delete pClient;
      startNextClient ( );
      return true;
    }
  }
  return false;
}

int Manager::ServerInfo::clientCount ( )
{
  return (int)m_listOfClients.count ( );
}

bool Manager::ServerInfo::startNextClient ( )
{
  bool    bReturn = false;
  Client *pClient = NULL;
  QValueList<Client *>::iterator it = m_listOfClients.begin ( );
  // In the first run we count how many are currently running.
  unsigned int iCount = 0;

  while ( it != m_listOfClients.end ( ) )  {
    pClient = *it++;
    if ( ! pClient->waitingInQueue ( ) )
      iCount ++;
  }

  it = m_listOfClients.begin ( );
  while ( it != m_listOfClients.end  ( ) )  {
    if  ( iCount >= m_iConcurrentThreads )
      break;
    pClient = *it++;

    if ( pClient->sourceFileEntry ( ) )  {
      debug_out ( "Starting Client for <%s> waiting<%s>\n", pClient->sourceFileEntry()->qsDisplayName.ascii(), pClient->waitingInQueue ( ) ? "true" : "false");
    }
    else  {
      debug_out ( "Starting Client for <%s> waiting<%s>\n", pClient->buttonTransition()->qsName.ascii(), pClient->waitingInQueue ( ) ? "true" : "false");
    }

    if ( pClient->waitingInQueue ( ) )  {
         pClient->setWaiting ( false );
         pClient->startMe    ( );
         iCount ++;
         bReturn = true;
    }
  }
  return bReturn;
}

Manager::Manager ( )
{
}

Manager::~Manager ( )
{
  lock ( );
  QValueList<ServerInfo *>::iterator it = m_listOfServer.begin ( );
  while ( it != m_listOfServer.end ( ) )
    delete *it++;
  m_listOfServer.clear ( );
  unlock ( );
}

Manager::ServerInfo *Manager::getServerInfo ( unsigned long iServerIP )
{
  ServerInfo *pServerInfo;
  QValueList<ServerInfo *>::iterator it = m_listOfServer.begin ( );
  while ( it != m_listOfServer.end ( ) )  {
    pServerInfo = *it++;
    if ( pServerInfo->getIP ( ) == iServerIP )
      return pServerInfo;
  }
  return NULL;
}

Manager::ServerInfo *Manager::getServerInfo ( Client *pClient )
{
  ServerInfo *pServerInfo;
  QValueList<ServerInfo *>::iterator it = m_listOfServer.begin ( );
  while ( it != m_listOfServer.end ( ) )  {
    pServerInfo = *it++;
    if ( pServerInfo->hasClient ( pClient ) )
      return pServerInfo;
  }
  return NULL;
}

QStringList &Manager::getFilterNames ( )
{
  if ( m_listFilterNames.count ( ) > 0 )
    return m_listFilterNames;

  Utils theUtils;
  QStringList filterList;
  QString qsTempFile, qsFilterName, qsCommand;

  qsCommand = theUtils.getToolPath ( "qrender" );
  if ( qsCommand.isEmpty ( ) )  {
    qsCommand = Global::qsSystemPath + "/bin/qrender";
    QFileInfo fileInfo ( qsCommand );
    if ( ! fileInfo.exists ( ) )  {
      qsCommand = theUtils.checkForExe ( "qrender" );
      if ( qsCommand.isEmpty ( ) )
           qsCommand = "qrender";
    }
  }
  m_listFilterNames.clear ( );

  qsTempFile = Global::qsTempPath  +  "/filterList.txt";
  qsCommand += QString ( " -f >%1" ).arg   ( qsTempFile );
  int iRet   = system  ( qsCommand.ascii ( ) );

  //printf ( "%s\n", qsCommand.ascii ( ) );
  iRet = iRet; // quiet down the compiler.
  chmod ( qsTempFile.utf8 ( ), S_IRUSR | S_IWUSR | S_IWRITE | S_IROTH | S_IWOTH | S_IWGRP | S_IRGRP );

  // Next we read in the temp file
  QFileInfo fileInfo ( qsTempFile );
  if ( fileInfo.size ( ) > 10 )  {
    QFile file ( qsTempFile );
    if ( file.open ( IO_ReadOnly ) )  {
      while ( ! file.atEnd   ( ) ) {
        file.readLine ( qsFilterName, 4096 );
        qsFilterName  = qsFilterName.remove ( "\n" );
        if (  qsFilterName.length  ( ) > 0 )
          m_listFilterNames.append ( qsFilterName );
      }
    }
    file.close ( );
  }

  if ( m_listFilterNames.size ( ) < 1 )  {
    // Hardcode at least the Cross Fade and KenBurns filter.
    m_listFilterNames.append ( "Cross Fade" );
//    m_listFilterNames.append ( "Ken Burns"  );
  }
  return m_listFilterNames;
}

bool Manager::registerEntry ( SourceFileEntry *pEntry )
{
  if ( ! pEntry || ! pEntry->bIsSlideshow || pEntry->listFileInfos.count ( ) < 1 )
    return false;

  SourceFileInfo *pInfo = pEntry->listFileInfos[0];
  if ( ! pInfo || ! pInfo->pSlideshow )
    return false;

  // Check if we need to create a new object ...
  if ( m_pSelf == NULL )
       m_pSelf = new Manager;

/* For as long as I develop this Client, we can disregard this check
   This check will make sure a SourceFileEntry ( Slideshow ) is only rendered once.
  QValueList<Client *>::iterator it = m_pSelf->m_listOfClients.begin ( );
  while ( it != m_pSelf->m_listOfClients.end ( ) )  {
    if  ( pEntry == (*it++)->sourceFileEntry ( ) )
      return false;
  }
*/
  usleep ( 10000 ); // wait 10 ms to avoid double clicks to regiseter multiple instances
  unsigned long iServerIP = 0x0F000001;  // 127.0.0.1
  ServerInfo *pServerInfo = m_pSelf->getServerInfo ( iServerIP );
  if ( ! pServerInfo )  {
         pServerInfo =   new ServerInfo ( iServerIP, SERVER_PORT, Global::iMaxRunningThreads );
         m_pSelf->m_listOfServer.append ( pServerInfo );
  }

//  Client *pNewClient = pServerInfo->addClient ( pEntry );
//  pNewClient->start ( );
  pServerInfo->addClient ( pEntry );
  pServerInfo->startNextClient  ( );

  sdebug_out ( " - and start <%s>\n", pEntry->qsDisplayName.ascii ( ) );
  return true;
}

bool Manager::unregisterEntry ( SourceFileEntry *pEntry )
{
  bool bReturn = false;
  if ( ! pEntry )
    return false;

  // Check if we need to create a new object ...
  if ( m_pSelf == NULL )
       m_pSelf = new Manager;

  m_pSelf->lock ( );

  ServerInfo *pServerInfo = NULL;
  QValueList<ServerInfo *>::iterator it = m_pSelf->m_listOfServer.begin ( );
  while ( it != m_pSelf->m_listOfServer.end ( ) )  {
    pServerInfo = *it++;
    if ( pServerInfo->removeClient  ( pEntry ) )  {
      if ( pServerInfo->clientCount ( )  < 1 )  {
        m_pSelf->m_listOfServer.remove ( pServerInfo );
        delete pServerInfo;
      }
      bReturn = true;
      break;
    }
  }

  m_pSelf->unlock ( );
  return bReturn;
}

bool Manager::registerTrans ( ButtonTransition *pTrans )
{
  if ( ! pTrans || pTrans->bFinishedRendering )
    return false;

  // Check if we need to create a new object ...
  if ( m_pSelf == NULL )
       m_pSelf = new Manager;

//  usleep ( 10000 ); // wait 10 ms to avoid double clicks to regiseter multiple instances
  unsigned long iServerIP = 0x0F000001;  // 127.0.0.1
  ServerInfo *pServerInfo = m_pSelf->getServerInfo ( iServerIP );
  if ( ! pServerInfo )  {
         pServerInfo =   new ServerInfo ( iServerIP, SERVER_PORT, Global::iMaxRunningThreads );
         m_pSelf->m_listOfServer.append ( pServerInfo );
  }

  pServerInfo->addClient ( pTrans );
  pServerInfo->startNextClient  ( );

  sdebug_out ( " - and start <%s>\n", pTrans->qsName.ascii ( ) );

  return true;
}

bool Manager::unregisterTrans ( ButtonTransition *pTrans )
{
  bool bReturn = false;
  if ( ! pTrans )
    return false;

  // Check if we need to create a new object ...
  if ( m_pSelf == NULL )
       m_pSelf = new Manager;

  m_pSelf->lock ( );

  ServerInfo *pServerInfo = NULL;
  QValueList<ServerInfo *>::iterator it = m_pSelf->m_listOfServer.begin ( );
  while ( it != m_pSelf->m_listOfServer.end ( ) )  {
    pServerInfo = *it++;
    if ( pServerInfo->removeClient  ( pTrans ) )  {
      if ( pServerInfo->clientCount ( )  < 1 )  {
        m_pSelf->m_listOfServer.remove ( pServerInfo );
        delete pServerInfo;
      }
      bReturn = true;
      break;
    }
  }

  m_pSelf->unlock ( );
  return bReturn;
}

void Manager::lock ( )
{
  m_mutex.lock ( );
}

void Manager::unlock ( )
{
  m_mutex.unlock ( );
}

/**********************************************************
 **
 **  The Client to the Rendering process ( Server )
 **
 **********************************************************/
Client::Client ( SourceFileEntry *pEntry )
  : QObject ( ), QThread ( )
{
  initMe ( );
  m_pSourceFileEntry = pEntry;
}

Client::Client ( ButtonTransition *pTrans )
  : QObject ( ), QThread ( )
{
  initMe ( );
  m_pButtonTransition = pTrans;
}

void Client::initMe ( )
{
  m_pSourceFileEntry  = NULL;
  m_pButtonTransition = NULL;
  m_bRemoteServer     = true;
  m_bSendImageFiles   = true;
  m_bWaitInQueue      = true;
  m_bSentAllInfo      = false;
  m_fProgress         = 0.0f;
  m_pFile             = NULL;
  m_iMessageID        = 0;
  m_iMessageLen       = 0;
  m_iRetryCounter     = 3;  // Try three times to re-establish a lost connection
  m_iKeepaliveTimer   = 0;
  m_pSocket = new QSocket ( this );
  connect ( m_pSocket, SIGNAL ( connected        ( ) ), SLOT ( socketConnected ( ) ) );
  connect ( m_pSocket, SIGNAL ( readyRead        ( ) ), SLOT ( socketReadyRead ( ) ) );
  connect ( m_pSocket, SIGNAL ( error        ( int ) ), SLOT ( socketError ( int ) ) );
  connect ( m_pSocket, SIGNAL ( connectionClosed ( ) ), SLOT ( socketConnectionClosed ( ) ) );
}

Client::~Client ( )
{
  enableTimer ( false );
  debug_out ( "Client DESTRUCTOR\n" );
  if ( m_pSocket )   {
    if ( ( m_pSocket->state ( ) != QSocket::Closing ) && ( m_pSocket->state ( ) != QSocket::Idle ) )
           m_pSocket->close ( );
    delete m_pSocket;
    m_pSocket = NULL;
  }
}

void Client::enableTimer ( bool bEnable )
{
  if ( bEnable )  {
    m_iKeepaliveTimer = startTimer ( I_AM_ALIVE_INTERVAL ); // check every 20 secs if we lost contact to the server
    m_lastPing = QDateTime::currentDateTime ( );
  }
  else  {
    killTimer ( m_iKeepaliveTimer );
    m_iKeepaliveTimer = 0;
  }
}

void Client::slotReconnect ( )
{
  Manager::ServerInfo *pServerInfo = Manager::m_pSelf->getServerInfo ( this );
  if ( pServerInfo )
       pServerInfo->setStarting ( false );

  if ( m_pSocket )
       m_pSocket->connectToHost ( "localhost", 5066 );

  debug_out ( "\n" );
}

void Client::closeConnection ( )
{
  debug_out ( "\n" );
  m_pSocket->close ( );
  if ( m_pSocket->state ( ) == QSocket::Closing ) // We have a delayed close.
      connect ( m_pSocket, SIGNAL ( delayedCloseFinished ( ) ), SLOT ( socketClosed ( ) ) );
  else // The socket is closed.
      socketClosed ( );
}

// Data received from the QRender server
// Note: if QRender and QDVDAuthor are on the same computer
//       then we do NOT send the files over the socket
//       but instead send only the file names.
void Client::socketReadyRead ( )
{
  Q_ULONG iLen = m_pSocket->bytesAvailable ( );
  while ( iLen > 0 )  {
    if  ( iLen < sizeof ( Q_UINT16 ) )
      return;

    Q_UINT16 iPrev = m_iMessageID;
    if ( m_iMessageID == 0 )  {
      char cMsgID  [ sizeof ( Q_UINT16 ) ];
      Q_LONG iRead1 = m_pSocket->readBlock ( cMsgID,  sizeof ( Q_UINT16 ) );
      if ( iRead1 == sizeof ( Q_UINT16 ) )
        m_iMessageID  = *(Q_UINT16 *)cMsgID;
    }
    debug_out ( " prev<0x%04X> curr<0x%04X>\n", iPrev, m_iMessageID );

    switch ( m_iMessageID )  {
      case SERVER_YOUR_PROGRESS_SIR:
        receivedProgress ( );
      break;
      case SERVER_ITS_ME_AND_I_AM_AT:  // The first response from the server is its version and host name
        receivedVersionAndHost ( );
      break;
      case SERVER_MY_STATUS_SIRE: // Status after exchange of XML file
        receivedServerState ( );
      break;
      case SERVER_I_AM_ALIVE:  // Will detect if the server went down or is unreachable
        receivedPing ( );      // Timer
      break;
      case SERVER_STICK_A_FORK_IN_ME:  // Will detect if the server went down or is unreachable
        receivedServerDone ( );
      break;
      case SERVER_GOT_A_PIECE: // QDVDAuthor sending files to server and server ack
        receivedFileAck ( );   // the reception so I can go ahead and send the next chunk
      break;
      case SERVER_TAKE_A_FILE: // The final vob file is coming in ...
        fileFromSocket ( );
        return;
      default:
        debug_out ( " Unknown MsgID : prevMsgID<0x%04X> currMsgID<0x%04X>\n", iPrev, m_iMessageID );
      break; // don't do nothin'
    }        // end switch statement
    m_iMessageID  = 0;
    m_iMessageLen = 0LL;
    iLen = m_pSocket->bytesAvailable ( );
  }
}

void Client::receivedProgress ( )
{
  double fProgress;
  char cMsgLen [ sizeof ( Q_UINT64 ) ];
  m_pSocket->readBlock  ( cMsgLen, sizeof ( Q_UINT64 ) );
  m_pSocket->readBlock  ( (char *)&fProgress, sizeof ( double ) );
  // Modify Client's data
  lock   ( );
  m_fProgress = fProgress;
  unlock ( );

  m_lastPing = QDateTime::currentDateTime ( );
  displayProgress ( );
}

void Client::receivedPing ( )
{
  // dully noted !
  // Need to implement the detection of WENT_MISSING_AFTER event
  // This message should come in every I_AM_ALIVE_INTERVAL mseconds
  // Only exception is when the final VOB file is sent back to QDVDAuthor
  // we disable this timer.

  m_lastPing = QDateTime::currentDateTime ( );
  debug_out ( "Received PING from Server\n" );
}

void Client::timerEvent ( QTimerEvent *pEvent )
{
/* Somehow I get remote server even though it should always be local ( for now ).
Manager::ServerInfo *p = Manager::m_pSelf->getServerInfo ( this );
printf ( "in <%p>\n", p );
if ( ! p )
  return;
in_addr in;
in.s_addr = p->getIP   (  );
const char *pServer = inet_ntoa ( in );
printf ( "bRemoteServer<%d> IP=<0x%016X> = <%s>\n", m_bRemoteServer, p->getIP ( ), pServer );
*/

  if ( pEvent->timerId ( ) == m_iKeepaliveTimer )  {
    debug_out ( " last<%s> Curr<%s>\n", m_lastPing.toString ( ).ascii ( ), QDateTime::currentDateTime ( ).toString ( ).ascii ( ) );

    if ( m_lastPing.secsTo ( QDateTime::currentDateTime ( ) ) > WENT_MISSING_AFTER * 2 ) { //I_AM_ALIVE_INTERVAL )  {
      // Oh crap happened ... should I try to restart ?
      // Should I notify the user
      // should I handle it internally ?f
      Manager::ServerInfo *pServerInfo = Manager::m_pSelf->getServerInfo ( this );
      if ( ! pServerInfo )
        return;

      if ( m_bRemoteServer )  {
        if ( ! pServerInfo->inRecovery ( ) )  {
          QString qsHtml, qsTime, qsServer;
          in_addr in;
          in.s_addr = pServerInfo->getIP   (  );
          const char *pServer = inet_ntoa ( in );

          qsTime  = QString ( "%1" ).arg ( (int)(WENT_MISSING_AFTER * I_AM_ALIVE_INTERVAL / 1000.0 ) );
          qsServer= QString ( "%1:%2" ).arg ( pServer ).arg ( pServerInfo->getPort ( ) );

          qsHtml  = tr ( "I could not reach the server %1 for %2 seconds.\n" ).arg ( qsServer ).arg ( qsTime );
          qsHtml += tr ( "Please check if the server is still running and restart if neccesary.\n\n" );

          qsHtml += tr ( "Once the server is responsive again, QDVDAuthor will reestablish the connection\n" );
          qsHtml += tr ( "between QDVDAuthor and QRender.\n" );

          MessageBox::html ( NULL, tr ( "Render server down" ), qsHtml, QMessageBox::Ok, QMessageBox::NoButton );
          pServerInfo->setRecovery ( true );
        }
      }
      else  {
        startServer ( );
        pServerInfo->setRecovery ( true );
        // run ( );
        //start ( );
        QDataStream stream  ( m_pSocket );
        stream << (Q_UINT16)CLIENT_GO_TO_WORK << (Q_UINT64)0LL;
        m_pSocket->flush ( );
      }
    }
    else
      m_lastPing = QDateTime::currentDateTime ( );
  }
}

void Client::receivedFileAck ( )
{
  Q_UINT64 iReceived;
  QDataStream stream ( m_pSocket );
  stream >> iReceived;

  if ( THROTTLE )
    usleep ( THROTTLE ); // throttle a bit
  sendNextPacket ( );
}

void Client::receivedVersionAndHost ( )
{
  QString qsHostName, qsVersion;
  char cMsgLen [ sizeof ( Q_UINT64 ) ];
  //Q_LONG iRead2 = m_pSocket->readBlock ( cMsgLen, sizeof ( Q_UINT64 ) );
  m_pSocket->readBlock ( cMsgLen, sizeof ( Q_UINT64 ) );
  m_iMessageLen = *(Q_UINT64 *)cMsgLen;

  QDataStream ds ( m_pSocket );
  ds >> qsHostName >> qsVersion;

  char cHostName[1024];
  gethostname ( (char *)&cHostName, 1024 );
  QString qsClientHost ( cHostName       );
  if ( qsClientHost == qsHostName )
    m_bRemoteServer  = false;
#ifdef FAKE_REMOTE_HOST
  m_bRemoteServer = true;
#endif

  // On a local host the wakeAll can come in before the wait is entered, thus we give it a shot break here
  usleep     ( 10000 );  // 10 msec
  m_waiter.wakeAll ( );
}

void Client::receivedServerState ( )
{
  QString qsStatus;
  char cMsgLen [ sizeof ( Q_UINT64 ) ];

  m_iMessageLen  = 0L;
  Q_ULONG iAvail = 0L;
  Q_ULONG iRead  = 0L;
  if ( m_pSocket->state ( ) == QSocket::Connected )  {
    iAvail  = m_pSocket->bytesAvailable ( );
    if ( iAvail > 0 )  {
      iRead = m_pSocket->readBlock ( cMsgLen, sizeof ( Q_UINT64 ) );
      if ( iRead > 0 )  {
        m_iMessageLen = *(Q_UINT64 *)cMsgLen;

        QDataStream ds ( m_pSocket );
        ds >> qsStatus;

        if ( qsStatus == "SameXML" ) 
          m_bSendImageFiles = false;
      }
      else  {
        QSocketDevice::Error err = m_pSocket->socketDevice ( )->error ( );
        printf ( "%s::%d > There was an Socket Error <%d>\n", __FILE__, __LINE__, err );
      }
    }
  }

  usleep ( 100000 ); // 10 ms break ...
  qApp->processEvents ( );
  m_waiter.wakeAll    ( );
}

void Client::receivedServerDone ( )
{
  enableTimer ( false );

  QString qsStatus;
  char cMsgLen [ sizeof ( Q_UINT64 ) ];
  memset ( &cMsgLen, 0, sizeof ( Q_UINT64 ) );

  Q_ULONG iAvail = 0L;
  Q_ULONG iRead  = 0L;
  if ( m_pSocket->state ( ) == QSocket::Connected )  {
    iAvail  = m_pSocket->bytesAvailable ( );
    if ( iAvail > 0 )  {
      iRead = m_pSocket->readBlock ( cMsgLen, sizeof ( Q_UINT64 ) );
      if ( iRead > 0 )  {
        Q_UINT64 iLen = *(Q_UINT64 *)cMsgLen;

        if ( iLen < 10 )  { // Should be a length of 4 ( "Done" )
          QDataStream ds ( m_pSocket );
          ds >> qsStatus;
        }
        else  {
          QSocketDevice::Error err = m_pSocket->socketDevice ( )->error ( );
          printf ( "%s::%d > There was an Socket Error <%d>\n", __FILE__, __LINE__, err );
        }
      }
    }
  }

  QCustomEvent *pNewEvent = new QCustomEvent ( EVENT_RENDER_EXIT );
  pNewEvent->setData      ( (void *) this );
  QApplication::postEvent ( Global::pApp, pNewEvent );
}

void Client::killServerThread ( )
{
  QDataStream stream ( m_pSocket );
  stream << (Q_UINT16)CLIENT_KILL_YOURSELF << (Q_UINT64)0LL;
  m_pSocket->flush ( );
}

bool Client::sendFile ( QString qsFileName )
{
  if ( m_pFile )
    return false;

  if ( m_bRemoteServer )  {
    m_pFile = new QFile ( qsFileName );
    if ( ! m_pFile->exists ( ) )  {
      delete m_pFile;
      m_pFile = NULL;
      return false;
    }
    m_pFile->open ( IO_ReadOnly );
  }

  // This will kick off the sendPacket protocol until the whole file is transmittet.
  QDataStream stream ( m_pSocket );
  stream << (Q_UINT16)CLIENT_TAKE_A_FILE << (Q_UINT64)m_pFile->size ( );
  stream << qsFileName;

  // This function will wait until the whole file has been sent to the server.
  m_waiter.wait ( ); // in mSec or ULONG_MAX

  return true;
}

void Client::sendNextPacket ( )
{
  char data[BLOCK_LEN];
  if ( ! m_pFile )
    return;

  int  iLen  = m_pFile->readBlock ( data, BLOCK_LEN );
  if ( iLen != -1 )  {
    m_pSocket->writeBlock ( data, iLen );
    Q_ULONG iPos  = m_pFile->at   ( );
    Q_ULONG iSize = m_pFile->size ( );

    if ( iPos >= iSize ) {
      m_pFile->close   ( );
      m_pSocket->flush ( );
      delete m_pFile;
      m_pFile = NULL;
      usleep ( 100 );
      m_waiter.wakeAll ( );
    }
//    emit sendingNonAckPercent ( fPercentage );
  }
}

bool Client::sendVersionAndHostRequest ( )
{
  char cHostName[1024];
  gethostname ( (char *)&cHostName, 1024 );
  QString  qsHostName  ( cHostName       );

  QDataStream stream ( m_pSocket );
  stream << (Q_UINT16)CLIENT_WHO_AND_WHERE_ARE_YOU << (Q_UINT64)qsHostName.length ( );
  stream << qsHostName;
  m_pSocket->flush ( );
  debug_out ( " <0x%04X>\n", CLIENT_WHO_AND_WHERE_ARE_YOU );
  return m_waiter.wait ( 30000 ); // wait for 30 seconds max
}

void Client::startMe ( )
{
  m_iRetryCounter = 3;
  QTimer::singleShot ( 10, this, SLOT ( slotConnectAndStart ( ) ) );
}

void Client::slotConnectAndStart ( )
{
  if ( m_pSocket->state ( ) == QSocket::Connected )  {
    usleep ( 10000 ); // in case we are not yet out of run()
    start  ( ); // -=> Execue run() in new thread
    return;
  }
  else if ( m_pSocket->state ( ) == QSocket::Idle )  {
    disconnect ( m_pSocket, SLOT   ( socketConnected ( ) ) );
    connect    ( m_pSocket, SIGNAL ( connected ( ) ), SLOT ( socketConnectAndStart ( ) ) );
    startServer( );
    usleep  ( 10000 );  // wait 10 milli second for the connection to happen.
    return;
  }
  if ( m_iRetryCounter-- > 0 )
    QTimer::singleShot ( 1000, this, SLOT ( slotConnectAndStart ( ) ) );
  else  {
    debug_out ( "Giving up trying to connect to server.\n" );
  }
}

void Client::socketConnectAndStart ( )
{
  disconnect ( m_pSocket, SLOT   ( socketConnectAndStart ( ) ) );
  connect    ( m_pSocket, SIGNAL ( connected ( ) ), SLOT ( socketConnected ( ) ) );
  start ( );
}

void Client::socketConnected ( )
{
  debug_out ( "\n" );
  m_waiter.wakeAll ( );
}

void Client::socketConnectionClosed ( )
{
  debug_out ( "Connection Closed By server : progress<%f>\n", m_fProgress );
  if ( ( m_fProgress < 100.0f ) && (  m_fProgress > 0.0f  ) )
    restartServer ( );
}

void Client::socketClosed ( )
{
  debug_out ( "Connection closed\n" );
}

void Client::socketError ( int e )
{
  debug_out ( "ErroNumber<%d>\n", e );
  if ( m_iRetryCounter > 0 )  {
    m_iRetryCounter --;
    // Here is the reconnection protocol
    // 1) Try to re-establish connection with server
    //    - If server is dead, then ( 3 times )
    //      o check if we have already sent all info.
    //      o try to restart server
    //      o try to reconnect
    //    - If connection is re-established, then
    //      o send msg RECONNECT ( worker id or worker name )
    //      o If server finds info, then send current progress
    //      o If server cant find info, then delete this Client and re-register
//    if ( ! m_bSentAllInfo )  {
      Client::reRequest ( this );
      return;
//    }
    debug_out ( "Retry<%d>\n", m_iRetryCounter );

    startServer ( );
    m_iRetryCounter = 5;
    QTimer::singleShot ( 1000, this, SLOT ( slotRecovered ( ) ) );
  }
}

void Client::slotRecovered ( )
{
  // Called afer socket error.
  if ( m_pSocket && m_pSocket->state ( ) == QSocket::Connected )  {
    QString qsName;
    if ( sourceFileEntry ( ) )
      qsName = sourceFileEntry  ( )->qsDisplayName;
    else if (  buttonTransition ( ) )
      qsName = buttonTransition ( )->qsName;

    debug_out ( "Reconnecting for <%s>[%u]\n", qsName.ascii ( ), (Q_UINT16)qsName.length ( ) );
    QDataStream stream ( m_pSocket );
    stream << (Q_UINT16)CLIENT_RECONNECTING << (Q_UINT16)qsName.length ( );
    stream << qsName;
    m_pSocket->flush ( );
    m_waiter.wait ( 30000 ); // wait for 30 seconds max
  }
  else if ( m_iRetryCounter > 0 )  {
    m_iRetryCounter --;
    debug_out ( "Trying again <%d> left\n",  m_iRetryCounter );
    QTimer::singleShot  ( 1000, this, SLOT ( slotRecovered ( ) ) );
  }
  else  {
    // Can not establish connection, try the whole rendering again
    Client::reRequest ( this );
  }
}

void Client::reRequest ( Client *pSelf )
{
  if ( pSelf->sourceFileEntry(  )  )  {
    sdebug_out ( "\n" );
    SourceFileEntry *pEntry = pSelf->sourceFileEntry ( );
    Manager::unregisterEntry ( pEntry );
    Manager::registerEntry   ( pEntry );
  }
  else if ( pSelf->buttonTransition ( ) )  {
    sdebug_out ( "\n" );
    ButtonTransition *pTransition = pSelf->buttonTransition ( );
    Manager::unregisterTrans ( pTransition );
    Manager::registerTrans   ( pTransition );
  }
}

void Client::startServer ( )
{
  Manager::ServerInfo *pServerInfo = Manager::m_pSelf->getServerInfo ( this );
  if ( pServerInfo )
       pServerInfo->startServer ( );

//  QTimer::singleShot ( 1000, this, SLOT ( slotReconnect ( ) ) );
  // We must be blocking here otherwise the timer would be fired way later
  // if the main loop is busy E.g. creating meus from a plugin
  usleep ( 100000 );
  slotReconnect  ( );
  debug_out ( "start server\n" );
}

void Client::restartServer ( )
{
  for ( int t=0; t<3; t++ )  {
    // if no connection after three attempts, then restart the server ...
    slotReconnect ( );
    if ( ( m_pSocket->state ( ) == QSocket::Connected  ) ||
         ( m_pSocket->state ( ) == QSocket::Connecting ) ) {
       debug_out ( "Reconnected\n" );
       return;
    }
    usleep ( 1000000 );  // Now wait a second ...
  }
  startServer  ( );
  usleep ( 10000 );  // give it 10 milli seconds
  if ( running ( ) )
    terminate  ( );

  start  ( );
  debug_out ( "restart\n" );
}

void Client::run ( )
{
  debug_out ( "\n" );
  m_bSentAllInfo = false;

  if ( m_pSocket->state ( ) == QSocket::Idle )  {
    startMe ( );
    return;
//    startServer  ( );
//    usleep ( 10000 );  // wait 10 milli second for the connection to happen.
//    for ( int t=0; t<5; t++ )  {
//      if ( m_pSocket->state ( ) != QSocket::Connected )
//        usleep ( 1000000 );  // Now wait a second ...
//    }
  }
  if ( m_pSocket->state ( ) != QSocket::Connected )  {
    QString qsName = m_pSourceFileEntry ? m_pSourceFileEntry->qsDisplayName : m_pButtonTransition->qsName;
    debug_out ( "Giving up connecting to the server for %s.\n", qsName.ascii ( ) );
    return;
  }
  // Executed in background thread and active as long as the rendering process is active for 
  // this SourceFileEntry.
  // Here is what we have to do;
  // 1) send slideshow XML file
  // 2) receive status from rendering process
  //    - New slideshow
  //    - Slideshow is already in the making
  //    - Slideshow was interrupted ( crash )
  //    - Slideshow ( .vob file ) already exists ( and log is clear )
  // 3) send all image files
  //    - Note: if on same server, then we can omit this step
  // 4) receive progress notification
  // 5) Finish up.
  if ( m_pSourceFileEntry )
    runEntry ( );
  else if ( m_pButtonTransition )
    runTrans ( );

  m_bSentAllInfo = true;
}

void Client::runSlideshow ( CXmlSlideshow *pSlideshow )
{
  if ( ! pSlideshow )
    return;

  Utils theUtils;
  // Now that the connection is/should be established, we can start the Keepalive - timer.
  enableTimer ( true );

  if ( m_pSourceFileEntry )  {
    debug_out ( "Run for entry<%s> \n", m_pSourceFileEntry->qsDisplayName.ascii ( ) );
  }
  else if ( m_pButtonTransition )  {
    debug_out ( "Run for entry<%s> \n", m_pButtonTransition->qsName.ascii ( ) );
  }

  int iCounter = 0;
  while ( iCounter++ <  10 )  {
    if  ( m_pSocket->state ( ) != QSocket::Connected )  {
      // wait for the connection event to kick in.
      debug_out ( "before WAIT <%d> \n", iCounter );
      m_waiter.wait  ( 1000 ); // in mSec or ULONG_MAX
      usleep ( 100000 );  // Socket just connected. Give it 10 msec.
    }
  }

  if ( ! sendVersionAndHostRequest ( ) )  {
    // Timed out, Assume local host.
    debug_out ( "After 30 seconds : did not receive ServerVersionAndHost response\n" );
    m_bRemoteServer = false;
    m_waiter.wakeAll ( );
  }

  QString qsFileName = pSlideshow->slideshow_name;
  qsFileName.replace ( "/", "_" );
  if ( m_pButtonTransition )
    qsFileName = theUtils.getTempFile ( "" ) + m_pButtonTransition->qsName + "/" + qsFileName + ".xml";
  else
    qsFileName = theUtils.getTempFile ( qsFileName + ".xml" );

  pSlideshow->writeXml ( qsFileName );

  QDataStream stream   (  m_pSocket );

  if ( m_bRemoteServer )  {
    sendFile ( qsFileName );

    // At this point we'll wait for the server to say something about the status.
    // There are two conditions to abort at this point
    // Slideshow already exists ( Server will now send the slideshow file )
    // Another process is already creating the slideshow.
    iCounter = 0;
    while ( iCounter < 50 )  {
      if  ( m_waiter.wait ( 100 ) )
            iCounter = 50;
    }

    if ( m_bSendImageFiles )  {
      uint t, iCount = pSlideshow->countImg ( );
      CXmlSlideshow::img_struct *pXmlImg = NULL;
      for ( t=0; t<iCount; t++ )  {
        pXmlImg = pSlideshow->getImg ( t );
        if ( ! pXmlImg )
          continue;
        sendFile ( pXmlImg->src );
      }
      for ( t=0; t<pSlideshow->audio_list.count ( ); t++ )
        sendFile ( pSlideshow->audio_list[t] );
    }
  }
  else  {
    QString qsTempPath = theUtils.getTempFile ( QString ( ) );
    if ( m_pButtonTransition )
      qsTempPath += m_pButtonTransition->qsName + "/";
    stream << (Q_UINT16)CLIENT_TAKE_THAT_XML << (Q_UINT64)( qsFileName.length ( ) + qsTempPath.length ( ) );
    stream << qsFileName << qsTempPath;
    m_pSocket->flush ( );
    usleep ( 1000000 );
  }
  stream << (Q_UINT16)CLIENT_GO_TO_WORK << (Q_UINT64)0LL;

  debug_out ( "Get the server to work ... msgID<0x%04X>\n",  CLIENT_GO_TO_WORK );
  m_pSocket->flush ( );
  setWaiting ( false );
}

void Client::runEntry ( )
{
  if ( ! m_pSourceFileEntry || ( m_pSourceFileEntry->listFileInfos.count ( ) < 1 ) )
    return;

  CXmlSlideshow *pSlideshow = m_pSourceFileEntry->listFileInfos[0]->pSlideshow;

  runSlideshow ( pSlideshow );
}

void Client::runTrans ( )
{
  if ( ! m_pButtonTransition )
    return;

  CXmlSlideshow *pSlideshow = m_pButtonTransition->createSlideshow ( );
  runSlideshow ( pSlideshow );

  // Finally we can destroy the temp slideshow object. Thanks for existing.
  delete pSlideshow;
}

void Client::lock ( )
{
  m_mutex.lock ( );
}

void Client::unlock ( )
{
  m_mutex.unlock ( );
}

SourceFileEntry *Client::sourceFileEntry ( )
{
  return m_pSourceFileEntry;
}

ButtonTransition *Client::buttonTransition ( )
{
  return m_pButtonTransition;
}

float Client::progress ( )
{
  float fReturn;
  lock ( );
  fReturn = m_fProgress;
  unlock ( );
  return fReturn;
}

bool Client::waitingInQueue ( )
{
  return m_bWaitInQueue;
}

void Client::setWaiting ( bool bWaitInQueue )
{
  m_bWaitInQueue = bWaitInQueue;
}

void Client::displayProgress ( )
{
  // QDVDAuthor::customEvent will call progress() to determinte the current progress.
  QCustomEvent *pNewEvent = new QCustomEvent ( EVENT_RENDER_PROGRESS );
  pNewEvent->setData      ( (void *) this );
  QApplication::postEvent ( Global::pApp, pNewEvent );
}

Q_UINT64 Client::fileFromSocket ( )
{
  Q_ULONG iBytesAvailable = 0LL;
  if ( ! m_pFile )  {
    Utils theUtils;
    QString qsFileName;

    char cMsgLen [ sizeof ( Q_UINT64 ) ];
    //Q_LONG iRead2 = m_pSocket->readBlock ( cMsgLen, sizeof ( Q_UINT64 ) );
    m_pSocket->readBlock ( cMsgLen, sizeof ( Q_UINT64 ) );
    m_iMessageLen = *(Q_UINT64 *)cMsgLen;

    QDataStream ds ( m_pSocket );
    ds >> qsFileName;

    debug_out ( "<%s>\n", (const char *)qsFileName.ascii ( ) );
    // Store under e.g. /tmp/qrender/MySlide/MySlide.vob
    QFileInfo fileInfo ( qsFileName );
    QString qsExt, qsNewFileName;

    qsNewFileName = Global::qsTempPath + "/" + Global::qsProjectName + "/" + fileInfo.fileName ( );

    fileInfo.setFile     ( qsNewFileName );
    if ( fileInfo.exists ( ) && fileInfo.size ( ) > 100 )  {
      // At this point we detected a file with the same ame exists already.
      // So we should ask for permission to delete it or move it out of the way.
      if ( MessageBox::warning ( NULL, tr ( "Slideshow Exists Already" ), tr ( "The slideshow <%1> exists already.\nDo you want to Delete the current file ( Yes ) or do you want to move it out of the way ( no ) ?" ).arg ( qsNewFileName ), QMessageBox::Yes, QMessageBox::No ) == QMessageBox::Yes )  {
        QFile temp ( qsNewFileName );
        if ( ! temp.remove ( ) )  {
          QString qsOrigFileName = qsNewFileName;
          qsNewFileName = theUtils.getUniqueTempFile ( qsNewFileName );
          MessageBox::warning ( NULL, tr ( "Could not delete file" ), tr ( "I could not delete the file <%1>.\nI will save the incomming slideshow under :\n%2" ).arg ( qsOrigFileName ).arg ( qsNewFileName ), QMessageBox::Ok, QMessageBox::NoButton );
        }
      }
    }
    enableTimer ( false );
    m_pFile = new QFile ( qsNewFileName );
    m_pFile->open ( IO_WriteOnly );
  }
  else  {
    iBytesAvailable = m_pSocket->bytesAvailable ( );
    QByteArray data ( iBytesAvailable );
    Q_ULONG iActualRead = m_pSocket->readBlock ( data.data ( ), iBytesAvailable );
//printf ( "writeBlock <%s> bytesAvail<%ld> readIn<%ld> fSize<%ld> vs msgLen<%ld>\n", m_pFile->name ( ).ascii ( ), iBytesAvailable, iActualRead, m_pFile->size ( ), m_iMessageLen );
    m_pFile->writeBlock ( data.data ( ), iActualRead );

    // Note: to be removed later ???
    m_pFile->flush ( );
  }

  // Let the Server know that I am ready for the next image.
  QDataStream stream ( m_pSocket );
  stream << (Q_UINT16)CLIENT_GOT_A_PIECE << (Q_UINT64)m_pFile->size ( );

  m_fProgress = 0.0;
  if ( m_iMessageLen > 0 )
       m_fProgress = 100.0 * ( 1.0 - (double)m_pFile->size ( ) / m_iMessageLen );
  displayProgress ( );

//printf ( "Render::fileFromSocket <%s> size<%lld> msgLen<%lld>\n", (const char *)m_pFile->fileName ( ).toUtf8 ( ), m_pFile->size  ( ), m_iMessageLen );
  if ( m_pFile->size  ( ) >= (Q_ULONG)m_iMessageLen )  {
       m_pFile->flush ( );
       m_pFile->close ( );

       delete m_pFile;
       m_pFile = NULL;
       m_iMessageID  = 0;
       m_iMessageLen = 0LL;

       QCustomEvent *pNewEvent = new QCustomEvent ( EVENT_RENDER_EXIT );
       pNewEvent->setData      ( (void *) this );
       QApplication::postEvent ( Global::pApp, pNewEvent );
       enableTimer ( false );
  }
  m_pSocket->flush ( );

  return iBytesAvailable;
}

}; // end of namespace Render
