/* 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 <BlinkenLib/BlinkenFrame.h>

#include "Directory.h"
#include "File.h"
#include "InStreamFile.h"
#include "Mgrs.h"
#include "Module.h"
#include "OutStreamFile.h"
#include "Transformer.h"
#include "StreamRecv.h"
#include "Transform.h"
#include "TransformFile.h"

namespace Blinker {

/**
 * @brief constructor
 * @param[in] name module name
 * @param[in] mgrs managers
 * @param[in] dirBase base directory
 */
Transformer::Transformer(const std::string &name, Mgrs &mgrs,
                 const Directory &dirBase):
  Module(name, mgrs, dirBase),
  m_fileInStream(dirBase.getFile("instream"), mgrs.m_streamMgr),
  m_fileTransform(dirBase.getFile("transform")),
  m_fileOutStream(dirBase.getFile("outstream"), mgrs.m_streamMgr)
{
  // set up
  getTransform();
  m_fileInStream.setStreamRecv(this);
}

/// virtual destructor
Transformer::~Transformer()
{
  // clean up
  m_fileInStream.setStreamRecv(NULL);
}

/// check for update of configuration
void Transformer::updateConfig()
{
  // stream name or transform file was modified -> re-get stream or transform
  if (m_fileInStream.checkModified())
    m_fileInStream.update();
  if (m_fileTransform.checkModified())
    getTransform();
  if (m_fileOutStream.checkModified())
    m_fileOutStream.update();
}

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

/// (re-)get transformation to apply
void Transformer::getTransform()
{
  // read transformation from transformation file
  m_fileTransform.update();

  // send current frame to output stream
  sendFrame();
}

/// send current frame to output stream
void Transformer::sendFrame()
{
  // get current frame from input stream and process it
  procFrame(m_fileInStream.getCurFrame());
}

/**
 * @brief process frame
 * @param[in] pFrame frame to process (NULL for none)
 */
void Transformer::procFrame(stBlinkenFrame *pFrame)
{
  stBlinkenFrame *pProcFrame;

  // no frame or no transformation -> pass "no frame"
  if (!pFrame || !m_fileTransform.m_valid) {
    m_fileOutStream.setFrame(NULL);
    return;
  }

  // no transformation -> pass frame directly
  if (m_fileTransform.m_obj.m_type == Transform::None) {
    m_fileOutStream.setFrame(pFrame);
    return;
  }

  // clone frame
  pProcFrame = BlinkenFrameClone(pFrame);
  if (!pProcFrame){
    m_fileOutStream.setFrame(NULL);
    return;
  }

  // transform frame
  switch (m_fileTransform.m_obj.m_type) {
    case Transform::None:
      // this case is handled above, just handle it here to avoid warning
      break;
    case Transform::RotCW:
      BlinkenFrameRotateCw(pProcFrame);
      break;
    case Transform::RotCCW:
      BlinkenFrameRotateCcw(pProcFrame);
      break;
    case Transform::RotHalf:
      BlinkenFrameRotateHalf(pProcFrame);
      break;
    case Transform::MirHor:
      BlinkenFrameMirrorHor(pProcFrame);
      break;
    case Transform::MirVer:
      BlinkenFrameMirrorVer(pProcFrame);
      break;
    case Transform::MirDiag:
      BlinkenFrameMirrorDiag(pProcFrame);
      break;
    case Transform::MirDiag2:
      BlinkenFrameMirrorDiag2(pProcFrame);
      break;
  }

  // pass processed frame to output stream
  m_fileOutStream.setFrame(pProcFrame);

  // free cloned frame
  BlinkenFrameFree(pProcFrame);
}

} // namespace Blinker

