/* 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_GAME_H
#define BLINKER_GAME_H

#include <map>
#include <set>
#include <string>
#include <vector>

#include <BlinkenLib/BlinkenFrame.h>

#include "Color.h"
#include "ColorFile.h"
#include "File.h"
#include "Format.h"
#include "FormatFile.h"
#include "LockNameFile.h"
#include "Mgrs.h"
#include "Module.h"
#include "NameFile.h"
#include "OpConn.h"
#include "OutStreamFile.h"
#include "Time.h"
#include "TimeCallee.h"
#include "UIntFile.h"

namespace Blinker {

/// base class for games
class Game: public Module, public TimeCallee
{
protected:
  /// raw color data matching image buffer
  typedef std::vector<unsigned char> ColorData;

  /// descriptor of value (e.g. a setting for the game)
  struct ValueDescr {
    unsigned int default_;
    unsigned int minimum;
    unsigned int maximum;
  };

private:
  /// set of operator connections
  typedef std::set<OpConn *> OpConnSet;

  /// sound (via sound name file) to play on operator connections
  typedef std::map<OpConn *, NameFile const *> OpConnSounds;

public:
  /**
   * @brief constructor
   * @param[in] name module name
   * @param[in] mgrs managers
   * @param[in] dirBase base directory
   */
  Game(const std::string &name, Mgrs &mgrs, const Directory &dirBase);

  /// virtual destructor
  virtual ~Game();

private:
  /// copy constructor disabled
  Game(const Game &that);

  /// assignment operator disabled
  const Game & operator=(const Game &that);

public:
  /// check for update of configuration
  virtual void updateConfig() final;

  /**
   * @brief check for update of configuration (derived game)
   * @param[in,out] doReinit set to true to ask for reinitialization
   * @param[in,out] doRedraw set to true to ask for redrawing
   */
  virtual void updateConfigGame(bool &doReinit, bool &doRedraw) = 0;

  /// callback when requested time reached
  virtual void timeCall();

protected:
  /// re-initialize game (e.g. due to config change)
  virtual void reinitialize() = 0;

  /// redraw current game image, expected to call sendFrame() at end
  virtual void redraw() = 0;

  /// process next time step of game
  virtual void timeStep() = 0;

  /**
   * @brief check if game can be actiavted: if interlock is free
   * @return whether game can be activated (activate() might still fail)
   */
  bool canActivate();

  /**
   * @brief activate game:
   *        take interlock, set up image buffer, call redraw()
   * @return whether game could be activated
   */
  bool activate();

  /**
   * @brief deactivate game:
   *        tear down image buffer, deactivate output, release interlock
   */
  void deactivate();

  /// check if game is active
  bool isActive() const;

  /// check if integer is between minimum and maximum values
  static bool checkLimitInt(int i, int min, int max)
  {
    return i >= min && i <= max;
  }

  /// limit integer to minimum and maximum values
  static void limitInt(int &i, int min, int max)
  {
    if (i < min) {
      i = min;
    }
    if (i > max) {
      i = max;
    }
  }

  /// check if range of two integers is nonEmpty and limit the integers
  static bool checkIntRangeLimit(int &i1, int &i2, int min, int max)
  {
    if (i1 > i2) {
      return false;
    }
    limitInt(i1, min, max);
    limitInt(i2, min, max);
    return true;
  }

  /// set pixel in image buffer
  void pixel(int y, int x, ColorData const &cd);

  /// draw horizontal line to image buffer
  void lineHor(int y, int x1, int x2, ColorData const &cd);

  /// draw vertical line to image buffer
  void lineVert(int y1, int y2, int x, ColorData const &cd);

  /// draw non-filled rectangle to image buffer
  void rect(int y1, int y2, int x1, int x2, ColorData const &cd);

  /// draw filled rectangle to image buffer
  void rectFill(int y1, int y2, int x1, int x2, ColorData const &cd);

  /**
   * @brief draw a small digit (3x5 pixels) to image buffer
   * @param[in] topY y coordinate of top of digit
   * @param[in] leftX x coordinate of left of digit
   * @param[in] digit to draw ('0'..'9')
   * @param[in] cd color to use for drawing
   */
  void digit3x5(int topY, int leftX, char digit, ColorData const &cd);

  /**
   * @brief draw small digits (3x5 each, 1 pixel gap) to image buffer
   * @param[in] topY y coordinate of top of digits
   * @param[in] leftX x coordinate of left of digits
   * @param[in] digits to draw (string of '0'..'9')
   * @param[in] cd color to use for drawing
   */
  void digits3x5(int topY, int leftX, std::string const &digits,
                 ColorData const &cd);

  /**
   * @brief draw number using 3x5 digits to image buffer
   * @param[in] anchorY y coordinate of anchor point
   * @param[in] anchorX x coordinate of anchor point
   * @param[in] alignY y alignment, -1 for top, 0 for center, 1 for bottom
   * @param[in] alignX x alignment, -1 for left, 0 for center, 1 for right
   * @param[in] number non-negative number to draw
   * @param[in] cd color to use for drawing
   */
  void number3x5(int anchorY, int anchorX, int alignY, int alignX,
                 int number, ColorData const &cd);

  /**
   * @brief process update of color file
   * @param[in] colorFile file containing color as string
   * @param[in] def default color (grayscale) in case file is invalid (0..255)
   * @pram[out] data color data
   * @return true on update
   */
  bool colorUpdate(ColorFile &colorFile, unsigned char def,
                   ColorData &data) const;

  /**
   * @brief convert color to raw color data
   * @param[in] colorFile file containing color as string
   * @param[in] def default color (grayscale) in case file is invalid (0..255)
   * @pram[out] data color data
   */
  void color2data(ColorFile const &colorFile, unsigned char def,
                  ColorData &data) const;

  /// process update of value file, return true on update
  static bool valueUpdate(UIntFile &valueFile, ValueDescr const &descr,
                          unsigned int &value);

  /// get value from value file
  static void valueFromFile(UIntFile &valueFile, ValueDescr const &descr,
                            unsigned int &value);

  /// process update of sound name file
  static void soundUpdate(NameFile &soundFile);

  /// send current image buffer as frame to output stream
  void sendFrame();

  /// set next game time step - plan timed action of game
  void setTimeStep(const Time& timeStepTime);

  /// unset game time step - do timed action for game
  void unsetTimeStep();

  /// play a sound on operator connection (NULL ok) (direct)
  void playOpConnSound(OpConn *pConn, NameFile const &soundFile);

  //// request playing a sound on operator connection (NULL ok) (via time call)
  void requestOpConnSound(OpConn *pConn, NameFile const &soundFile);

  /// request closing operator connection (NULL ok) (closed via time call)
  void requestOpConnClose(OpConn *pConn);

  /// remove operator connection from requests (call when op conn is closed)
  void forgetOpConn(OpConn *pConn);

private:
  /// (re-)create image buffer
  void createImgBuf();

  /// tear down image buffer
  void destroyImgBuf();

  /// request next time call - or cancel request if not needed
  void planTimeCall();

protected:
  FormatFile    m_fileFormat;          ///< format file for output
  ColorFile     m_fileBackgroundColor; ///< color file for background color
  OutStreamFile m_fileOutStream;       ///< output stream name file
  int           m_height;              ///< height of image buffer
  int           m_width;               ///< width of image buffer
  int           m_channels;            ///< number of channels of image buffer
  ColorData     m_imgBuf;              ///< image buffer (empty if none)
  ColorData     m_backgroundColor;     ///< background color

private:
  LockNameFile m_fileLockName; ///< lock name file for game interlock
  bool         m_haveTimeStep; ///< if a time step is pending
  Time         m_timeStepTime; ///< time of next time step
  OpConnSounds m_opConnSounds; ///< sound to play on operator connections
  OpConnSet    m_opConnsClose; ///< operator connections to be closed
}; // class Game

} // namespace Blinker

#endif // #ifndef BLINKER_GAME_H

