/* 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_TETRIS_H
#define BLINKER_TETRIS_H

#include <string>
#include <vector>

#include <BlinkenLib/BlinkenFrame.h>

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

namespace Blinker {

/// tetris game
class Tetris: public Game, public OpReqIf
{
protected:
  /// indices of the stones
  enum StoneIndex {
    StoneI,
    StoneL,
    StoneJ,
    StoneT,
    StoneO,
    StoneZ,
    StoneS,
    StoneCnt ///< not an index, but number of stones
  };

  /// stone state
  enum State {
    Active, ///< stone is falling
    Fixed, ///< stone is fixed part of field
    StateCnt ///< not an index, but number of stone states
  };

  /// coordinates of a pixel part of a stone
  struct Coord {
    int x;
    int y;
  };

  /// descriptor of a certain rotation of a stone
  struct RotStone {
    Coord pixels[4]; ///< coordinates of the 4 pixels
  };

  /// descriptor of a stone
  struct Stone {
    RotStone rot[4]; ///< rotations of the stone
  };

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

  /// virtual destructor
  virtual ~Tetris();

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

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

public:
  /**
   * @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);

  /**
   * @brief check if accepting new operator connection is possible
   * @param[in] name operator interface name
   * @return if accepting new connection is possible
   */
  virtual bool acceptNewOpConn(const std::string &name);

  /**
   * @brief new operator connection
   * @param[in] name operator interface name
   * @param[in] pConn operator connection object
   *
   * The new connection may not yet be used for sending inside this callback.
   */
  virtual void newOpConn(const std::string &name, OpConn *pConn);

  /**
   * @brief key command received on operator connection
   * @param[in] pConn operator connection object
   * @param[in] key key that was pressed
   */
  virtual void opConnRecvKey(OpConn *pConn, char key);

  /**
   * @brief play command received on operator connection
   * @param[in] pConn operator connection object
   * @param[in] sound name of sound to play
   */
  virtual void opConnRecvPlay(OpConn *pConn, const std::string &sound);

  /**
   * @brief operator connection is closed
   * @param[in] pConn operator connection object
   *
   * The connection may not be used for sending any more in this callback.
   */
  virtual void opConnClose(OpConn *pConn);

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

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

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

  /// count time at end of game
  void timeGameOver();

  /// blink completed rows
  void timeBlinkRows();

  /// falling stone
  void timeStone();

  /// check for completed rows to disappear (new stone afterwards)
  void checkComplete();

  /// set up a new stone
  void newStone();

  /// set time for next time step of game - or unset if not needed
  void planTimeStep();

  /// get rotatation of stone from stone/rotation index (or NULL in invalid)
  static RotStone const * getRotStone(int stone, int rot);

  /// check if stone fits at position
  bool checkStoneFit(int stone, int rot, int y, int x) const;

  /// check if stone overflow game field
  bool checkStoneOverflow(int stone, int rot, int y, int x) const;

  /// freeze stone to field at position
  void freezeStone(int stone, int rot, int y, int x);

  /// draw a stone to image buffer
  void drawStone(int stone, int rot, int y, int x, State state);

  /// wipe a stone from image buffer (i.e. replace it with background color)
  void wipeStone(int stone, int rot, int y, int x);

  /// set shape of stone to color in image buffer
  void colorStone(int stone, int rot, int y, int x, ColorData const &color);

protected:
  /// stone data (make sure it matches the StoneIndex enum)
  static Stone const c_stones[StoneCnt];

  static int const c_stoneCnt; ///< number of stones
  static int const c_rotCnt;   ///< number of rotations per stone
  static int const c_pixelCnt; ///< number of pixels per stone

  /// descriptor for delay value
  static ValueDescr const c_delayDescr;

  /// descriptor for delay value during dropping a stone
  static ValueDescr const c_dropDelayDescr;

  /// descriptor for delay value during blinking of disappearing rows
  static ValueDescr const c_blinkDelayDescr;

  /// descriptor for delay value at end of game
  static ValueDescr const c_gameOverDelayDescr;

  ColorFile m_fileStoneIColor;   ///< color file for active I stone color
  ColorFile m_fileStoneLColor;   ///< color file for active L stone color
  ColorFile m_fileStoneJColor;   ///< color file for active J stone color
  ColorFile m_fileStoneTColor;   ///< color file for active T stone color
  ColorFile m_fileStoneOColor;   ///< color file for active O stone color
  ColorFile m_fileStoneZColor;   ///< color file for active Z stone color
  ColorFile m_fileStoneSColor;   ///< color file for active S stone color
  ColorFile m_fileFixedIColor;   ///< color file for fixed I stone color
  ColorFile m_fileFixedLColor;   ///< color file for fixed L stone color
  ColorFile m_fileFixedJColor;   ///< color file for fixed J stone color
  ColorFile m_fileFixedTColor;   ///< color file for fixed T stone color
  ColorFile m_fileFixedOColor;   ///< color file for fixed O stone color
  ColorFile m_fileFixedZColor;   ///< color file for fixed Z stone color
  ColorFile m_fileFixedSColor;   ///< color file for fixed S stone color
  UIntFile  m_fileDelay;         ///< file for initial delay in ms per frame
  UIntFile  m_fileDropDelay;     ///< file for delay while dropping (ms/frame)
  UIntFile  m_fileBlinkDelay;    ///< file for delay while blinking (ms/frame)
  UIntFile  m_fileGameOverDelay; ///< file for delay at end (ms/frame)

  NameFile m_fileStartSound;       ///< "start game" sound name file
  NameFile m_fileRowCompleteSound; ///< "row complete" sound name file
  NameFile m_fileGameOverSound;    ///< "game over" sound name file

  ColorData m_stoneColors[StateCnt][StoneCnt]; ///< stone colors

  unsigned int m_delay;         ///< initial delay in ms per frame
  unsigned int m_dropDelay;     ///< delay while dropping in ms per frame
  unsigned int m_blinkDelay;    ///< delay while rows blinking in ms per frame
  unsigned int m_gameOverDelay; ///< delay at end of game in ms per frame

  OpConn *m_pConn; ///< operator connection of player (or NULL)

  int  m_stone;     ///< index of current stone
  int  m_rot;       ///< rotation of current stone
  int  m_posX;      ///< x position of current stone
  int  m_posY;      ///< y position of current stone
  bool m_dropping;  ///< whether currently dropping a stone
  int  m_blinking;  ///< step of blinking: 0 not blinking, lsb == 0 -> visible
  int  m_completed; ///< number of overall completed rows
  bool m_gameOver;  ///< counter at end of game: 0 means game is running

  /// tetris field (y * m_width + x), -1 for free, >= 0 for pixel from stone
  std::vector<int> m_field;
  /// rows that are currently blinking (bool for each row, true when blinking)
  std::vector<bool> m_rowsBlink;
}; // class Tetris

} // namespace Blinker

#endif // #ifndef BLINKER_TETRIS_H

