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

#include <stdlib.h>
#include <string>
#include <string.h>

#include <BlinkenLib/BlinkenFrame.h>
#include <BlinkenLib/BlinkenProto.h>

#ifdef BLINKER_CFG_ETHERPIX
extern "C" {
#include <etherpix/etherpix.h>
} // extern "C"
#endif // #ifdef BLINKER_CFG_ETHERPIX

#include "Directory.h"
#include "File.h"
#include "EtherPix.h"
#include "InStreamFile.h"
#include "Mgrs.h"
#include "Module.h"
#include "Size.h"
#include "StreamRecv.h"
#include "Time.h"
#include "TimeCallee.h"

namespace Blinker {

/**
 * @brief constructor
 * @param[in] name module name
 * @param[in] mgrs managers
 * @param[in] dirBase base directory
 */
EtherPix::EtherPix(const std::string &name, Mgrs &mgrs,
                 const Directory &dirBase):
  Module(name, mgrs, dirBase),
  m_fileInStream(dirBase.getFile("instream"), mgrs.m_streamMgr),
  m_fileConfig(dirBase.getFile("etherpix.etp")),
  m_pDisplay(NULL)
{
  // set up
  m_fileInStream.setStreamRecv(this);
  createDisplay();
}

/// virtual destructor
EtherPix::~EtherPix()
{
  // clean up
  destroyDisplay();
  m_fileInStream.setStreamRecv(NULL);
  m_mgrs.m_callMgr.cancelTimeCall(this);
}

/// check for update of configuration
void EtherPix::updateConfig()
{
  // input stream name file was modified -> re-get input stream
  if (m_fileInStream.checkModified())
    m_fileInStream.update();

  // EtherPix config file was modified -> re-create display
  if (m_fileConfig.checkModified())
    createDisplay();
}

/**
 * @brief set current frame
 * @param[in] stream stream name
 * @param[in] pFrame current frame (NULL for none)
 */
void EtherPix::setFrame(const std::string &stream, stBlinkenFrame *pFrame)
{
  displayFrame(pFrame);
  (void)stream; // unused
}

/// callback when requested time reached
void EtherPix::timeCall()
{
  // refresh frame
  sendFrame();
}

/// (re-)create EtherPix display
void EtherPix::createDisplay()
{
#ifdef BLINKER_CFG_ETHERPIX
  destroyDisplay();

  // create a display
  m_pDisplay = etp_display_create(m_fileConfig.getPath().c_str(), NULL, NULL);
  if (!m_pDisplay)
    return;

  // get size of display
  etp_display_get_size(m_pDisplay, &m_size.m_width, &m_size.m_height);

  // output current frame
  displayFrame(m_fileInStream.getCurFrame());
#endif // #ifdef BLINKER_CFG_ETHERPIX
}

/// destroy EtherPix display
void EtherPix::destroyDisplay()
{
#ifdef BLINKER_CFG_ETHERPIX
  if (m_pDisplay) {
    etp_display_free(m_pDisplay);
    m_pDisplay = NULL;
  }
#endif // #ifdef BLINKER_CFG_ETHERPIX
}

/**
 * @brief display frame on EtherPix
 * @param[in] pFrame frame to display (or NULL)
 */
void EtherPix::displayFrame(stBlinkenFrame *pFrame)
{
#ifdef BLINKER_CFG_ETHERPIX
  char data[65536];
  bool haveData = false;
  etp_pixfmt_t pixfmt;
  stBlinkenFrame *pClonedFrame;
  int frameWidth, frameHeight, frameChannels, frameMaxval, channels;

  // leave if no display
  if (!m_pDisplay)
    return;

  // convert frame to needed size and then to MCUF data as EtherPix library
  // can read data section of MCUF packet

  // frame available
  if (pFrame) {
    frameWidth = BlinkenFrameGetWidth(pFrame);
    frameHeight = BlinkenFrameGetHeight(pFrame);
    frameChannels = BlinkenFrameGetChannels(pFrame);
    frameMaxval = BlinkenFrameGetMaxval(pFrame);
    // format matches
    // (size matches and 24bit RGB or monochrome as required by EtherPix)
    if (frameWidth == (int)m_size.m_width &&
        frameHeight == (int)m_size.m_height &&
        (frameChannels == 1 || frameChannels == 3) &&
        frameMaxval == 255) {
      // convert to MCUF packet
      haveData = BlinkenFrameToNetwork(pFrame, BlinkenProtoMcuf,
                                       data, sizeof(data)) >= 0;
      channels = frameChannels;
    }
    // format does not match
    else {
      // convert format: clone and resize
      pClonedFrame = BlinkenFrameClone(pFrame);
      if (pClonedFrame) {
        channels = frameChannels == 1 ? 1 : 3;
        BlinkenFrameResize(pClonedFrame, m_size.m_height,
                           m_size.m_width, channels, 255);
        // convert to MCUF packet
        haveData = BlinkenFrameToNetwork(pClonedFrame, BlinkenProtoMcuf,
                                         data, sizeof(data)) >= 0;
        // free cloned frame
        BlinkenFrameFree(pClonedFrame);
      }
    }
  }

  // data available -> to EtherPix display
  if (haveData) {
    pixfmt = channels == 1 ? etp_pixfmt_mono8 : etp_pixfmt_rgb24;
    etp_display_data_fmt(m_pDisplay, (etp_u8_t*)(data + 12),
                         channels, m_size.m_width * channels,
                         0, 0, m_size.m_width, m_size.m_height, pixfmt);
  // no data available -> clear EtherPix display
  } else {
    etp_display_data_clear(m_pDisplay);
  }

  // send configured frame
  sendFrame();
#else // #ifdef BLINKER_CFG_ETHERPIX
  (void)pFrame;
#endif // #ifdef BLINKER_CFG_ETHERPIX else
}

/// (re-)send frame to EtherPix
void EtherPix::sendFrame()
{
#ifdef BLINKER_CFG_ETHERPIX
  if (m_pDisplay) {
    etp_display_send(m_pDisplay);
    m_mgrs.m_callMgr.requestTimeCall(this, Time::now() + Time(1)); // refresh
  }
#endif // #ifdef BLINKER_CFG_ETHERPIX
}

} // namespace Blinker

