/* 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 "Filter.h"
#include "Format.h"
#include "FormatFile.h"
#include "InStreamFile.h"
#include "Mgrs.h"
#include "Module.h"
#include "OutStreamFile.h"
#include "Size.h"
#include "SizeFile.h"
#include "StreamRecv.h"
#include "UInt.h"
#include "UIntFile.h"

namespace Blinker {

/**
 * @brief constructor
 * @param[in] name module name
 * @param[in] mgrs managers
 * @param[in] dirBase base directory
 */
Filter::Filter(const std::string &name, Mgrs &mgrs,
               const Directory &dirBase):
  Module(name, mgrs, dirBase),
  m_fileInStream(dirBase.getFile("instream"), mgrs.m_streamMgr),
  m_fileFormat(dirBase.getFile("format")),
  m_fileSize(dirBase.getFile("size")),
  m_fileWidthMin(dirBase.getFile("width_min")),
  m_fileWidthMax(dirBase.getFile("width_max")),
  m_fileHeightMin(dirBase.getFile("height_min")),
  m_fileHeightMax(dirBase.getFile("height_max")),
  m_fileChannelsMin(dirBase.getFile("channels_min")),
  m_fileChannelsMax(dirBase.getFile("channels_max")),
  m_fileColorsMin(dirBase.getFile("colors_min")),
  m_fileColorsMax(dirBase.getFile("colors_max")),
  m_fileOutStream(dirBase.getFile("outstream"), mgrs.m_streamMgr)
{
  // set up
  getSettingsIfChanged(true);
  m_fileInStream.setStreamRecv(this);
}

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

/// check for update of configuration
void Filter::updateConfig()
{
  // stream name or format file was modified -> re-get stream or format
  if (m_fileInStream.checkModified())
    m_fileInStream.update();
  getSettingsIfChanged(false);
  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 Filter::setFrame(const std::string &stream, stBlinkenFrame *pFrame)
{
  procFrame(pFrame);
  (void)stream; // unused
}

/**
 * @brief (re-)get settings that changed
 * @param[in] force if true: (re-)get all settings w/o checking for change
 */
void Filter::getSettingsIfChanged(bool force)
{
  bool change = false;

  // re-read modified files
  if (force || m_fileFormat.checkModified()) {
    m_fileFormat.update();
    change = true;
  }
  if (force || m_fileSize.checkModified()) {
    m_fileSize.update();
    change = true;
  }
  if (force || m_fileWidthMin.checkModified()) {
    m_fileWidthMin.update();
    change = true;
  }
  if (force || m_fileWidthMax.checkModified()) {
    m_fileWidthMax.update();
    change = true;
  }
  if (force || m_fileHeightMin.checkModified()) {
    m_fileHeightMin.update();
    change = true;
  }
  if (force || m_fileHeightMax.checkModified()) {
    m_fileHeightMax.update();
    change = true;
  }
  if (force || m_fileChannelsMin.checkModified()) {
    m_fileChannelsMin.update();
    change = true;
  }
  if (force || m_fileChannelsMax.checkModified()) {
    m_fileChannelsMax.update();
    change = true;
  }
  if (force || m_fileColorsMin.checkModified()) {
    m_fileColorsMin.update();
    change = true;
  }
  if (force || m_fileColorsMax.checkModified()) {
    m_fileColorsMax.update();
    change = true;
  }

  // send current frame to output stream if anything changed
  if (change)
    sendFrame();
}

/// send current frame to output stream
void Filter::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 Filter::procFrame(stBlinkenFrame *pFrame)
{
  unsigned int width, height, channels, maxval, colors;

  // leave the following do/while loop with break to pass "no frame"
  do {

    // no input frame -> pass "no frame"
    if (!pFrame)
      break;

    // get frame format
    width = BlinkenFrameGetWidth(pFrame);
    height = BlinkenFrameGetHeight(pFrame);
    channels = BlinkenFrameGetChannels(pFrame);
    maxval = BlinkenFrameGetMaxval(pFrame);
    colors = maxval + 1;

    // if frame does not satisfy any filter -> pass "no frame" (via break)

    // format setting present -> check that format matches
    if (m_fileFormat.m_valid &&
        (width != m_fileFormat.m_obj.m_width ||
         height != m_fileFormat.m_obj.m_height ||
         channels != m_fileFormat.m_obj.m_channels ||
         maxval != m_fileFormat.m_obj.m_maxval))
      break;

    // size setting present -> check that size matches
    if (m_fileSize.m_valid &&
        (width != m_fileSize.m_obj.m_width ||
         height != m_fileSize.m_obj.m_height))
      break;

    // if present, check min/max settings for width/height/channels/colors
    if ((m_fileWidthMin.m_valid && width < m_fileWidthMin.m_obj.m_uint) ||
        (m_fileWidthMax.m_valid && width > m_fileWidthMax.m_obj.m_uint) ||
        (m_fileHeightMin.m_valid && height < m_fileHeightMin.m_obj.m_uint) ||
        (m_fileHeightMax.m_valid && height > m_fileHeightMax.m_obj.m_uint) ||
        (m_fileChannelsMin.m_valid &&
         channels < m_fileChannelsMin.m_obj.m_uint) ||
        (m_fileChannelsMax.m_valid &&
         channels > m_fileChannelsMax.m_obj.m_uint) ||
        (m_fileColorsMin.m_valid && colors < m_fileColorsMin.m_obj.m_uint) ||
        (m_fileColorsMax.m_valid && colors > m_fileColorsMax.m_obj.m_uint))
      break;

    // frame satisfied all filters

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

    return;
  } while (0);

  // do/while loop has been left with break -> pass "no frame"
  m_fileOutStream.setFrame(NULL);
}

} // namespace Blinker

