#ifndef TT_HH
#define TT_HH

#include <vector>
#include <string>

struct tetris_piece {
  char piece[4][4];
};

// Tetris piece gallery
static const tetris_piece tt_types[] = {
  {
    {
      { ' ', ' ', ' ', ' ' },
      { '#', '#', '#', '#' },
      { ' ', ' ', ' ', ' ' },
      { ' ', ' ', ' ', ' ' }
    }
  },
  {
    {
      { ' ', ' ', ' ', ' ' },
      { ' ', '#', '#', ' ' },
      { ' ', '#', '#', ' ' },
      { ' ', ' ', ' ', ' ' }
    }
  },
  {
    {
      { ' ', ' ', ' ', ' ' },
      { ' ', '#', '#', '#' },
      { ' ', '#', ' ', ' ' },
      { ' ', ' ', ' ', ' ' }
    }
  },
  {
    {
      { ' ', ' ', ' ', ' ' },
      { '#', '#', '#', ' ' },
      { ' ', ' ', '#', ' ' },
      { ' ', ' ', ' ', ' ' }
    }
  },
  {
    {
      { ' ', ' ', ' ', ' ' },
      { '#', '#', ' ', ' ' },
      { ' ', '#', '#', ' ' },
      { ' ', ' ', ' ', ' ' }
    }
  },
  {
    {
      { ' ', ' ', ' ', ' ' },
      { ' ', ' ', '#', '#' },
      { ' ', '#', '#', ' ' },
      { ' ', ' ', ' ', ' ' }
    }
  }
};
static const int num_tt_types = sizeof(tt_types) / sizeof(tt_types[0]);

class tetris_board {
public:
  // Empty class just for throwing
  class CreateBoardError {};

  // Create a board of width w and height h
  tetris_board(int w=10, int h=10): fixed_board(h), width(w), height(h) {
    if (w <= 0 || h <= 0)
      throw CreateBoardError();
    restart();
  }
  // Resize the board, clear its content.
  void resize(int w, int h) {
    if (w <= 0 || h <= 0)
      throw CreateBoardError();
    fixed_board = std::vector<std::string>(h);
    width = w;
    height = h;
    restart();
  }
  // Clear the board
  void restart() {
    for (int i = 0; i < height; ++i)
      fixed_board[i] = std::string(width, ' ');
    cp_x = (width - 1)/2;
    cp_y = 0;
    curr_piece = tt_types[rand_piece_no()];
    if (!valid_board())
      throw CreateBoardError();
    next_piece = tt_types[rand_piece_no()];
    update_vboard();
  }
  // Get the piece down one row
  // Return value:
  //   0: New piece is created
  //   1: Game over
  //   2: continue
  int down(int &nr) {
    ++cp_y;
    if (valid_board()) {
      update_vboard();
      return 2;
    }
    --cp_y;
    nr = get_new_piece();
    if (valid_board())
      return 0;
    else
      return 1;
  }
  // General movement of current piece
  // All of them are based on the idea to try the move, check whether the
  // result is valid, and if not reverse the move.
  bool shift_left() {
    --cp_x;
    if (valid_board()) {
      update_vboard();
      return true;
    }
    ++cp_x;
    return false;
  }
  bool shift_right() {
    ++cp_x;
    if (valid_board()) {
      update_vboard();
      return true;
    }
    --cp_x;
    return false;
  }
  bool rot_cw() {
    tetris_piece old_piece = curr_piece;
    for (int i = 0; i < 4; ++i)
      for (int j = 0; j < 4; ++j)
	curr_piece.piece[i][j] = old_piece.piece[3-j][i];
    if (valid_board()) {
      update_vboard();
      return true;
    }
    curr_piece = old_piece;
    return false;
  }
  bool rot_ccw() {
    tetris_piece old_piece = curr_piece;
    for (int i = 0; i < 4; ++i)
      for (int j = 0; j < 4; ++j)
	curr_piece.piece[i][j] = old_piece.piece[j][3-i];
    if (valid_board()) {
      update_vboard();
      return true;
    }
    curr_piece = old_piece;
    return false;
  }
  // Return a reference to the visual board
  // App can keep it as long as it like,
  // and the value will be updated when functions are called.
  // It remains valid until tetris_board is destroyed.
  const std::vector<std::string>& get_board() {
    return vboard;
  }
  const tetris_piece& get_next_piece() {
    return next_piece;
  }
private:
  // Add up current piece and fixed_board to form vboard
  // This assumes current board to be valid!
  void update_vboard() {
    vboard = fixed_board;
    for (int i = 0; i < 4; ++i)
      for (int j = 0; j < 4; ++j)
	if (curr_piece.piece[i][j] == '#')
	  vboard[cp_y+i-1][cp_x+j-1] = '*';
  }
  // Check whether current piece and fixed_board adds up nicely
  bool valid_board() {
    for (int i = 0; i < 4; ++i)
      for (int j = 0; j < 4; ++j)
	if (curr_piece.piece[i][j] == '#') {
	  int y = cp_y + i - 1;
	  int x = cp_x + j - 1;
	  if (y < 0 || x < 0 || y >= height || x >= width ||
	      fixed_board[y][x] != ' ')
	    return false;
	}
    return true;
  }
  // Add the current piece into fixed_board,
  // turn next_piece to next_piece and reset its location,
  // and refill next_piece
  int get_new_piece() {
    for (int i = 0; i < 4; ++i)
      for (int j = 0; j < 4; ++j)
	if (curr_piece.piece[i][j] == '#')
	  fixed_board[cp_y+i-1][cp_x+j-1] = '#';
    int newi = height - 1;
    for (int i = height - 1; i; --i) {
      bool found = false;
      for (int j = 0; j < width; ++j)
	if (fixed_board[i][j] == ' ')
	  found = true;
      if (found) {
	fixed_board[newi] = fixed_board[i];
	--newi;
      }
    }
    for (int i = 0; i < newi; ++i)
      fixed_board[i] = std::string(width, ' ');
    curr_piece = next_piece;
    cp_x = (width - 1)/2; cp_y = 0;
    next_piece = tt_types[rand_piece_no()];
    update_vboard();
    return newi;
  }
  // Create a random piece, using the method suggested by rand(3)
  int rand_piece_no() {
    return static_cast<int>(num_tt_types*1.0*rand()/(RAND_MAX+1.0));
  }
  std::vector<std::string> vboard, fixed_board;
  tetris_piece curr_piece, next_piece;
  int cp_x, cp_y;
  int width, height;
};

#endif

