/* Blinker
   Copyright 2011-2019 Stefan Schuermans <stefan@blinkenarea.org>
   Copyleft GNU public license - http://www.gnu.org/copyleft/gpl.html
   a blinkenarea.org project */

#ifndef BLINKER_SYNCRECEIVER_IMPL_H
#define BLINKER_SYNCRECEIVER_IMPL_H

#include <stdint.h>
#include <string>
#include <string.h>

#include "Directory.h"
#include "File.h"
#include "IoCallee.h"
#include "Mgrs.h"
#include "Module.h"
#include "NetHost.h"
#include "OutSyncFile.h"
#include "Protocol.h"
#include "ProtocolFile.h"
#include "SettingFile.h"
#include "SyncRecv.h"
#include "SyncReceiver.h"

namespace Blinker {

/**
 * @brief constructor
 * @param[in] name module name
 * @param[in] mgrs managers
 * @param[in] dirBase base directory
 */
template<typename ADDR, typename SOCK>
SyncReceiver<ADDR, SOCK>::SyncReceiver(const std::string &name, Mgrs &mgrs,
                                       const Directory &dirBase):
  Module(name, mgrs, dirBase),
  m_fileOutSync(dirBase.getFile("outsync"), mgrs.m_syncMgr),
  m_fileBind(dirBase.getFile("bind")),
  m_fileSrc(dirBase.getFile("source")),
  m_pSock(NULL)
{
  // read source address
  m_fileSrc.update();

  // create and bind socket
  createSock();
}

/// virtual destructor
template<typename ADDR, typename SOCK>
SyncReceiver<ADDR, SOCK>::~SyncReceiver()
{
  // destroy socket
  destroySock();
}

/// check for update of configuration
template<typename ADDR, typename SOCK>
void SyncReceiver<ADDR, SOCK>::updateConfig()
{
  // output sync stream name file was modified -> re-get output sync stream
  if (m_fileOutSync.checkModified())
    m_fileOutSync.update();

  // bind address file was modified -> re-create socket
  if (m_fileBind.checkModified()) {
    destroySock();
    createSock();
  }

  // source address file was modified -> re-read source address
  if (m_fileSrc.checkModified())
    m_fileSrc.update();
}

/**
 * @brief callback when I/O object is readable
 * @param[in] io I/O object that is readable
 */
template<typename ADDR, typename SOCK>
void SyncReceiver<ADDR, SOCK>::ioReadCall(Io *io)
{
  // reception on socket
  if (io == m_pSock)
    receiveFromSock();
}

/**
 * @brief callback when I/O object is writable
 * @param[in] io I/O object that is writable
 */
template<typename ADDR, typename SOCK>
void SyncReceiver<ADDR, SOCK>::ioWriteCall(Io *io)
{
  (void)io; // unused
}

/// create socket and bind it
template<typename ADDR, typename SOCK>
void SyncReceiver<ADDR, SOCK>::createSock()
{
  // create socket
  if (!m_pSock) {
    m_pSock = new SOCK();
    if (!m_pSock)
      return;
  }

  // get bind address from bind address setting file
  m_fileBind.update();
  if (!m_fileBind.m_valid) {
    delete m_pSock;
    m_pSock = NULL;
    return;
  }

  // bind socket
  if (!m_pSock->bind(m_fileBind.m_obj)) {
    delete m_pSock;
    m_pSock = NULL;
    return;
  }

  // request callback on recepetion
  m_mgrs.m_callMgr.requestIoReadCall(this, m_pSock);
}

/// destroy socket
template<typename ADDR, typename SOCK>
void SyncReceiver<ADDR, SOCK>::destroySock()
{
  // cancel callback request
  m_mgrs.m_callMgr.cancelIoReadCall(this, m_pSock);

  // destroy socket
  if (m_pSock) {
    delete m_pSock;
    m_pSock = NULL;
  }
}

/// receive data from socket
template<typename ADDR, typename SOCK>
void SyncReceiver<ADDR, SOCK>::receiveFromSock()
{
  std::string data;
  ADDR addr;

  // make sure socket exists
  if (!m_pSock)
    return;

  // receive (leave if no reception)
  if (!m_pSock->recv(data, addr))
    return;

  // check source address (if configured)
  if (m_fileSrc.m_valid && addr != m_fileSrc.m_obj)
    return; // mismatch

  // process received synchronization information
  procInfo(data);
}

/**
 * @brief process synchronization information
 * @param[in] data received synchronization data
 */
template<typename ADDR, typename SOCK>
void SyncReceiver<ADDR, SOCK>::procInfo(const std::string &data)
{
  /// Po.W.E.R. (stage play by BBM) synchronization protocol
  struct PoSyPacket {
    char     magic[4]; ///< fixed magic, always "PoSy"
    uint32_t flags;    ///< flags, see PosSyFlags below
    char     name[64]; ///< name of current piece in playlist
    uint32_t pos_ms;   ///< position within current piece in milliseconds
  };

  /// flags for PoSyPacket
  enum PoSyFlags {
    PoSyPause = 0x00000001, ///< pause mode active
  };

  PoSyPacket     packet;
  SyncRecv::Info info;

  // parse frame, leave if invalid
  if (data.size() < sizeof(packet))
    return;
  memcpy(&packet, data.c_str(), sizeof(packet));
  if (memcmp(packet.magic, "PoSy", sizeof(packet.magic)) != 0)
    return;
  info.pause = (Net2Host32(packet.flags) & PoSyPause) != 0;
  info.name = packet.name;
  info.pos.fromMs(Net2Host32(packet.pos_ms));

  // pass on sync information
  m_fileOutSync.sendInfo(info);
}

} // namespace Blinker

#endif // #ifndef BLINKER_SYNCRECEIVER_IMPL_H

