/*****************************************************************************
 ** Class QRender::Render
 **
 ** 
 **   Created : Fri Jun 6 12:09:08 2008
 **        by : Varol Okan using kate editor
 ** Copyright : (c) Varol Okan
 ** License   : GPL v 2.0
 **
 **
 ** This class will be created by Render::Server, when a new 
 ** socket connection is established
 **
 ** 1) The first thing to happen is the exchange of the Host and Version
 ** 2) Next is the recetion of the Slideshow XML file
 ** 3) Followed by a check if the slideshow exists and is up-to-date
 ** 4) If not, then the client will send the image files
 ** 5) Then we render the Slideshow and
 ** 6) send the generated VOB file back to the client.
 **
 *****************************************************************************/

#include <unistd.h>

#include <QDir>
#include <QFile>
#include <QImage>
#include <QFileInfo>
#include <QByteArray>
#include <QTimerEvent>
#include <QApplication>

#include "utils.h"
#include "shared.h"
#include "render.h"
#include "slideshow.h"

// Note: Maybe later on I can import dvd-slideshow config files
#include "../qdvdauthor/xml_slideshow.h"


namespace Render
{

Render::Render ( int iSocketDescriptor, QObject *pParent )
     : QThread ( pParent ), m_iSocketDescriptor ( iSocketDescriptor )
{
  m_iMessageLen =    0;
  m_iMessageID  =    0;
  m_iTimerID    =   -1;
  m_iXMLHash    =  0LL;
  m_pFile       = NULL;
  m_pSlideshow  = NULL;
  m_bKillClient = false;
  m_qsTempPath  = QDir::tempPath ( ) + "/qrender/";
  m_bLocalClientAndServer = false;

  Utils theUtils;
  theUtils.recMkdir ( m_qsTempPath );

  QString qsLogFile, qsStatusFile;
  qsLogFile    = m_qsTempPath + "log.txt";
  qsStatusFile = m_qsTempPath + "status.txt";
  m_status.setLogInfo ( 0, qsStatusFile );
  m_logger.setLogInfo ( 3, qsLogFile    );
  m_logger.setLogObjectInfo ( true );
//printf ( "QRender::QRender <%p> thread<%X>\n", this, pthread_self ( ) );

  if ( ! m_tcpSocket.setSocketDescriptor ( m_iSocketDescriptor ) )  {
      emit ( error ( m_tcpSocket.error ( ) ) );
      return;
  }

  connect ( &m_tcpSocket, SIGNAL ( readyRead ( ) ), this, SLOT ( slotReadSocket ( ) ) );
  connect ( this,         SIGNAL ( finished  ( ) ), this, SLOT ( slotExiting    ( ) ) );
}

Render::~Render ( )
{
  if ( m_pSlideshow )
    delete m_pSlideshow;
  logger ( ).iLOG ( "Render::~Render <%p>\n", this );
}

void Render::run ( )
{
  // Kick off the pinger ...
  m_iTimerID = startTimer ( I_AM_ALIVE_INTERVAL );;

  logger ( ).iLOG ( "%s Build date : %s\n", QRENDER_VERSION, __DATE__ );
  status ( ).log  ( "START\n" );

  // Enter the TcpServers Event Loop to listen to events from the socket.
  int iExec = -1;
  while ( iExec != CLIENT_KILL_YOURSELF && ! m_bKillClient )  {
    iExec = exec ( );
    switch ( iExec )  {
      case CLIENT_GO_TO_WORK:
        createSlideshow ( );
      break;
      default:
      break;
    }
  }
  myExit ( 0 ); //iExec );
  killTimer ( m_iTimerID );
}

void Render::slotReadSocket ( )
{
  qint64 iBytesAvail = m_tcpSocket.bytesAvailable ( );
//printf ( "iBytesAvail<0x%016llX>\n", iBytesAvail );
  if  (  iBytesAvail <= 0 )
    return;

  while  ( true )  {
    if   ( m_iMessageLen == 0 )  {
      // Excuse me, come back when you're grown up.
      if ( iBytesAvail   < 10 )
        return;
      // Okay we have yet to see what message we just received
      QDataStream ds ( &m_tcpSocket );
      ds >> m_iMessageID;
      ds >> m_iMessageLen;
      iBytesAvail -= 10;

      logger ( ).dLOG ( "NewMessage<0x%04X> Len<%lld> bytesAvail<%lld>\n", m_iMessageID, m_iMessageLen, iBytesAvail );
    }

    // At this point we have one Message boy in the socket buffer
    switch ( m_iMessageID )      {
    case CLIENT_WHO_AND_WHERE_ARE_YOU:
      sendVersionAndHost ( );
    break;
    case CLIENT_TAKE_A_FILE:  {
      if   ( m_iMessageLen > 0 ) {
        if ( fileFromSocket(   ) == 0 )
          return; // all avail bytes were read. Okay
      }
    }
    break;
    case CLIENT_TAKE_THAT_XML:
      receivedXMLName ( );
    break;
    case CLIENT_GO_TO_WORK:
      exit ( CLIENT_GO_TO_WORK );  // will leave exec and continue in background thread ( in function run )
//      createSlideshow ( );
    break;
    case CLIENT_GOT_A_PIECE:
      sendNextPacket ( );
    break;
    case CLIENT_RECONNECTING:
      // This render object was created after a socket error
      receivedReconnect ( );
    break;
    case CLIENT_KILL_YOURSELF:
      m_bKillClient = true;  // slideshow will eventually terminate
//      exit ( CLIENT_KILL_YOURSELF );
    break;
    default:
      // Error, unknown messageID
      logger ( ).eLOG ( "NewMessage<0x%04X> Len<%lld> bytesAvail<%lld>\n", m_iMessageID, m_iMessageLen, iBytesAvail );
    break; // don't do nothin'
    }      // end switch statement
    m_iMessageID  = 0;
    m_iMessageLen = 0;
    iBytesAvail = m_tcpSocket.bytesAvailable ( );
  }
}

void Render::myExit ( int iExitCode )
{
  m_tcpSocket.flush   ( );
  m_tcpSocket.disconnectFromHost ( );
  qApp->processEvents ( );

  if ( m_tcpSocket.state ( ) != QAbstractSocket::UnconnectedState )
       m_tcpSocket.waitForDisconnected ( 5000 );  // wait for up to 5 seconds to be disconnected
  qApp->processEvents ( );

  QThread::exit ( iExitCode );
  logger ( ).iLOG ( "Server exited \n" );
  status ( ).log  ( "EXIT %d\n", iExitCode );
}

bool Render::killClient ( )
{
  return m_bKillClient;
}

void Render::sendProgress ( float fProgress )
{
  logger ( ).dLOG ( " <%f>\n", fProgress );
  status ( ).log  ( "PROGRESS:%f\n", fProgress );
  if ( m_tcpSocket.state ( ) != QAbstractSocket::ConnectedState )
    return;

  QByteArray block;
  // build the progress message
  block = Message<quint16>::create ( SERVER_YOUR_PROGRESS_SIR )   +
          Message<quint64>::create ( (quint64)sizeof ( double ) ) +
          Message<double>::create  ( (double)fProgress );

  // Send progress message
  m_tcpSocket.write ( block );
  m_tcpSocket.flush ( );
}

bool Render::isLocalServer ( )
{
  return m_bLocalClientAndServer;
}

Logger &Render::logger ( )
{
  return m_logger;
}

Logger &Render::status ( )
{
  return m_status;
}

void Render::sendMessage ( quint16 iMessageID, QString qsMessage )
{
  if ( m_tcpSocket.state ( ) != QAbstractSocket::ConnectedState )
    return;

  m_tcpSocket.write  ( Message<quint16>::create ( iMessageID ) );
  m_tcpSocket.write  ( Message<quint64>::create ( (quint64)qsMessage.length ( ) ) );

/*
quint64 iLen = (quint64)qsMessage.length ( );
char *cLen = (char *)&iLen;
printf ( "%s::%d > len<%llu> [%02x %02x %02x %02x]\n", __FILE__, __LINE__, iLen, cLen[0], cLen[1], cLen[2], cLen[3] );
*/

  QDataStream stream ( &m_tcpSocket );
  stream << qsMessage;

  m_tcpSocket.flush ( );
}

void Render::sendVersionAndHost ( )
{
  QString qsClientHost;
  char cHostName[1024];
  quint16 iMessageID = SERVER_ITS_ME_AND_I_AM_AT;
  QDataStream stream   ( &m_tcpSocket    );
  // At this point the socket shoould still have the hostname of the client intus.
  stream >> qsClientHost;

  // Okay lets compare if we are on the same host.
  gethostname ( (char *)&cHostName, 1024 );
  QString  qsHostName  ( cHostName       );
  QString  qsVersion   ( QRENDER_VERSION );
  if ( qsClientHost == qsHostName )
    m_bLocalClientAndServer = true;

  logger ( ).iLOG ( " Version<%d> client<%s>\n", m_bLocalClientAndServer, (const char *)qsClientHost.toAscii ( ) );

#ifdef FAKE_REMOTE_HOST
  m_bLocalClientAndServer = false;
#endif

  // And finally notify the client about our location and age.
  stream << iMessageID << (quint64)( qsHostName.length ( ) + qsVersion.length ( ) );
  stream << qsHostName << qsVersion;
  m_tcpSocket.flush ( );
}

void Render::sendServerState ( bool bSameSlideshow )
{
  QString qsState;

  // Okay lets compare if we are on the same host.
  if ( bSameSlideshow )
    qsState = "SameXML"; // Note, this string must be in synch with qdvdauthor/render_client.cpp::receivedServerState()

  logger ( ).iLOG ( " SameXML<%s>\n", bSameSlideshow ? "true" : "false" );
  status ( ).log  ( "SAME_FILE:%d",   bSameSlideshow );

  // And finally notify the client about our location and age.
  sendMessage ( SERVER_MY_STATUS_SIRE, qsState );

  m_tcpSocket.flush  ( );
}

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

  if ( m_tcpSocket.state ( ) != QAbstractSocket::ConnectedState )
    return false;

  m_pFile = new QFile ( qsFileName );
  if ( ! m_pFile->exists ( ) )  {
    delete m_pFile;
    m_pFile = NULL;
    return false;
  }
  m_pFile->open ( QIODevice::ReadOnly );
  killTimer ( m_iTimerID );

  logger ( ).iLOG ( " <%s> msgID<0x%04X>\n", (const char *)qsFileName.toAscii ( ), (quint16)SERVER_TAKE_A_FILE );
  status ( ).log  ( "SIZE:%lld\n", m_pFile->size ( ) );
  // This will kick off the sendPacket protocol until the whole file is transmittet.
  QByteArray cString = qsFileName.toUtf8 ( );
  m_tcpSocket.write  ( Message<quint16>::create ( SERVER_TAKE_A_FILE ) );
  m_tcpSocket.write  ( Message<quint64>::create ( (quint64)m_pFile->size ( ) ) );

  QDataStream stream ( &m_tcpSocket );
  stream << qsFileName;

  m_tcpSocket.flush  ( );

  return true;
}

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

  qint64 iLen  = m_pFile->read ( data, BLOCK_LEN );
  if (   iLen != -1 )  {
    m_tcpSocket.write (  data, iLen );
    qint64 iPos   = m_pFile->pos  ( );
    qint64 iSize  = m_pFile->size ( );

    if ( iPos >= iSize  ) {
      m_pFile->close    ( );
      m_tcpSocket.flush ( );
      logger ( ).iLOG ( " Finished <%s> WAKE ALL\n", (const char *)m_pFile->fileName ( ).toAscii ( ) );
      status ( ).log  ( "VIDEO DONE\n" );
      delete m_pFile;
      m_pFile = NULL;
      usleep ( 100 );
      m_waiter.wakeAll ( );
      usleep ( 100 );
      // Is this required ?
      m_iTimerID = startTimer ( I_AM_ALIVE_INTERVAL );
      exit ( 0 ); // exit the EventLoop. This will cause the function run to exit
    }
//printf ( "Client::sendNextPacket thr<%X> <%f%%>\n", (unsigned)pthread_self ( ), fPercentage );
  }
}

void Render::slotExiting ( )
{
  logger ( ).dLOG ( "\n" );
  emit   ( signalIAmDone ( this ) );
  QThread::deleteLater   ( );
}

void Render::timerEvent ( QTimerEvent *pEvent )
{
  if ( pEvent->timerId ( ) == m_iTimerID )  {
    if ( m_tcpSocket.state ( ) != QAbstractSocket::ConnectedState )
      return;

    logger ( ).iLOG ( " PING \n");
    // Every 20 seconds we'll send out a short ping.
    m_tcpSocket.write  ( Message<quint16>::create ( SERVER_I_AM_ALIVE ) );
    m_tcpSocket.flush  ( );
  }
}

quint64 Render::fileFromSocket ( )
{
  if ( ! m_pFile )  {
    Utils theUtils;
    QString qsFileName;
    QDataStream ds ( &m_tcpSocket );
    ds >> qsFileName;
    // printf ( "Render::fileFromSocket <%s>\n", (const char *)qsFileName.toAscii ( ) );
    // Store under e.g. /tmp/qrender/MySlide/MySlide.xml
    QFileInfo fileInfo ( qsFileName );
    QString qsExt, qsNewFileName;
    qsExt = fileInfo.suffix ( );

    if ( qsExt.toLower ( ) == "xml" )  {
      // The first file is the XmlSlideshow - file.
      m_qsTempPath  = QDir::tempPath ( ) + "/qrender/" + fileInfo.baseName ( ) + "/";
      theUtils.recMkdir ( m_qsTempPath );
      qsNewFileName = m_qsTempPath + fileInfo.fileName ( );
      m_iXMLHash    = createXMLHash ( qsNewFileName );
    }
    else
      qsNewFileName = getHashName ( qsFileName );

    m_pFile = new QFile  ( qsNewFileName );
    m_pFile->open ( QIODevice::WriteOnly );
    logger ( ).iLOG ( "Render::fileFromSocket new <%s> size<%lld> msgLen<%lld>\n", (const char *)m_pFile->fileName ( ).toUtf8 ( ), m_pFile->size  ( ), m_iMessageLen );
  }

  qint64 iBytesAvailable = m_tcpSocket.bytesAvailable ( );
  quint64 iFileSize = (quint64)m_pFile->size ( );
  quint64 iDelta    = 0LL;
  if ( iFileSize + iBytesAvailable > m_iMessageLen )  {
    // Seems like the next message is entangled in this one ...
    iDelta = ( iFileSize + iBytesAvailable ) - m_iMessageLen;
    iBytesAvailable -= iDelta;
  }

  QByteArray data ( iBytesAvailable, 0 );
  qint64 iActualRead = m_tcpSocket.read ( data.data ( ), iBytesAvailable );
  m_pFile->write  ( data.data ( ), iActualRead );

  // Let the Client know that I am ready for the next image.
  m_tcpSocket.write ( Message<quint16>::create ( SERVER_GOT_A_PIECE ) ); 
  m_tcpSocket.write ( Message<quint64>::create ( (quint64)m_pFile->size ( ) ) );

//printf ( "Render::fileFromSocket <%s> size<%lld> msgLen<%lld>\n", (const char *)m_pFile->fileName ( ).toUtf8 ( ), m_pFile->size  ( ), m_iMessageLen );
  if ( m_pFile->size  ( )  >=  (qint64)m_iMessageLen )  {
       m_pFile->flush ( );
       m_pFile->close ( );
       // the first file is the slideshow XML file
       if ( ! m_pSlideshow )  {
         bool bDifferentSlideshow = loadSlideshowXML ( m_pFile->fileName ( ) );
         status ( ).log ( "GOT XML\n" );
         sendServerState ( bDifferentSlideshow );
       }
       logger ( ).iLOG ( "Got all <%s> size<%lld> msgLen<%lld>\n", (const char *)m_pFile->fileName ( ).toUtf8 ( ), m_pFile->size  ( ), m_iMessageLen );

       delete m_pFile;
       m_pFile = NULL;
       m_iMessageID  = 0;
       m_iMessageLen = 0;
  }
  m_tcpSocket.flush ( );
//printf ( "Render::fileFromSocket Avail<%lld> read<%lld> delta<%lld>\n", iBytesAvailable, iActualRead, iDelta );
  return ( iBytesAvailable - iActualRead + iDelta );
}

void Render::receivedReconnect ( )
{
  logger ( ).iLOG ( "receiveReconnect \n" );
  QString qsName;

//  qint64  iBytesAvail = m_tcpSocket.bytesAvailable ( );
  quint16 iLength;

  QDataStream ds ( &m_tcpSocket );
  ds >> iLength;
  ds >> qsName;

//printf ( "%s::%d > bytesAvail<%lld> len<%u> Reconnect Name<%s>\n", __FILE__, __LINE__, iBytesAvail, iLength, (const char *)qsName.toUtf8 ( ) );
  logger ( ).iLOG ( "Reconnect Name<%s>\n", (const char *)qsName.toUtf8 ( ) );
  status ( ).log  ( "RECONNECT\n" );
}

void Render::receivedXMLName ( )
{
  QString qsFileName, qsTempPath;
  QDataStream ds ( &m_tcpSocket );
  ds >> qsFileName >> qsTempPath;

  m_qsClientTempPath  = qsTempPath;
  m_pSlideshow = new CXmlSlideshow;
  m_pSlideshow->readXml ( qsFileName, false );
  logger ( ).iLOG ( " XML File <%s> tempPath<%s>\n", (const char *)qsFileName.toUtf8 ( ), (const char *)qsTempPath.toUtf8 ( ) );
  status ( ).log  ( "GOT XML\n" );
}

QString Render::getHashName ( QString &qsFileName )
{
  // If server and client are on the same box, then we can simply use the original file
  if ( m_bLocalClientAndServer )  {
    QString qsFile = qsFileName;
    return  qsFile;
  }
  Utils theUtils;
  QString qsExt, qsHash, qsHashFileName;
  QFileInfo fileInfo      ( qsFileName );
  qsExt = fileInfo.suffix ( ).toLower( );

  unsigned long long iHashValue = theUtils.SDBMHash ( qsFileName );
  qsHash.sprintf ( "%016llX", iHashValue );
  qsHashFileName = m_qsTempPath + qsHash + "." + qsExt;
  return qsHashFileName;
}

quint64 Render::createXMLHash ( QString qsFileName )
{
  quint64 iXMLHash = 0LL;
  QFileInfo fileInfo ( qsFileName );
  if ( fileInfo.exists ( ) )   {
    // Okay, this kinda sucks but is a quick solution
    // Load the whole XML file into a QString and run 
    // the hash function over it
    // NOte: can be replaced later on with a more 
    // efficient way esp with large XML files
    Utils theUtils;
    QFile theFile ( qsFileName );
    theFile.open  ( QIODevice::ReadOnly );
#if (QT_VERSION > 0x0403FF)
    qint64 iSize = fileInfo.size ( );
    uchar *pFile = theFile.map   ( 0LL, iSize );
    if ( pFile )  {
      QString qsFileContents  ( (const char *)pFile );
      iXMLHash = theUtils.SDBMHash ( qsFileContents );
    }
#else
    QString qsContents  ( theFile.readAll ( ) );
    iXMLHash = theUtils.SDBMHash ( qsContents );
#endif
    theFile.close ( );
  }
  return iXMLHash;
}

bool Render::loadSlideshowXML ( QString qsFileName )
{
  bool bReturn = true;
  // The XML file has been transmitted.
  if   (  m_iXMLHash != 0LL )  {
    quint64 iXMLHash  = createXMLHash ( qsFileName );
    if (    iXMLHash == m_iXMLHash )  {
      // We can reasenably be sure the xml file has not changed
      // thus the current existing VOB file is up-to-date.
      logger ( ).iLOG ( "File <%s> seems to exist already (Hash=<0x%016llX>)\n", (const char *)qsFileName.toAscii ( ), iXMLHash );
      bReturn = false;
    }
  }

logger ( ).iLOG ( "Create Slideshow object(Hash=<0x%016llX>)\n", (const char *)qsFileName.toAscii ( ), m_iXMLHash );
  m_pSlideshow = new CXmlSlideshow;
  m_pSlideshow->readXml ( qsFileName, false );

  status ( ).log ( "LOAD XML\n" );
  return bReturn;
}

void Render::createSlideshow ( )
{
  if ( ! m_pSlideshow )  {
    logger ( ).log ( "Error, No Slideshow object created.\n" );
    return;
  }
  QString qsTempPath = m_qsTempPath;
  if ( isLocalServer ( ) )  // If local server, then let us create the VOB in the right place.
    qsTempPath = m_qsClientTempPath;

  Utils theUtils;
  theUtils.recMkdir ( qsTempPath );

  status ( ).log  ( "START SLIDESHOW\n" );

  Slideshow slide ( this );
  slide.initMe    ( m_pSlideshow, qsTempPath );
  if ( slide.exec ( ) )  {
    // Presumably success. Let us send the final product back to the client ( QDVDAuthor )
    QString qsFile = m_pSlideshow->slideshow_name;
    qsFile.replace ( "/", "_" );
    QString qsFileName = qsTempPath + qsFile + ".vob";
    QFileInfo fileInfo ( qsFileName );

    if  ( fileInfo.exists ( ) )  {
      if ( !isLocalServer ( ) )  // only send through the Socket if we are on a remote server.
        sendFile ( qsFileName );
      else  {
        sendMessage ( SERVER_STICK_A_FORK_IN_ME, tr ( "Done" ) );
        myExit ( 0 ); // exi the thread
      }
    }
    else  {
      sendMessage ( (quint16)SERVER_SHIT_HAPPENED, tr ( "Final slideshow file <%1> not found.\n" ).arg ( qsFileName ) );
      myExit ( -1 );
      //exit ( -1 ); // exit the EventLoop. This will cause the function run to exit
    }
  }
}

};  // End of namespace Render

