/*
 * libtcod CPP samples
 * This code demonstrates various usages of libtcod modules
 * It's in the public domain.
 */

// uncomment this to disable SDL sample (might cause compilation issues on some systems)
//#define NO_SDL_SAMPLE

#include <SDL.h>
#include <libtcod.h>

#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>

// a sample has a name and a rendering function
typedef struct {
  std::string name;
  void (*render)(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse);
} sample_t;

// sample screen size
#define SAMPLE_SCREEN_WIDTH 46
#define SAMPLE_SCREEN_HEIGHT 20
// sample screen position
#define SAMPLE_SCREEN_X 20
#define SAMPLE_SCREEN_Y 10

// ***************************
// samples rendering functions
// ***************************

// the offscreen console in which the samples are rendered
TCODConsole sampleConsole(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT);

// ***************************
// true colors sample
// ***************************
void render_colors(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  enum { TOPLEFT, TOPRIGHT, BOTTOMLEFT, BOTTOMRIGHT };
  static TCODColor cols[4] = {
      TCODColor(50, 40, 150),
      TCODColor(240, 85, 5),
      TCODColor(50, 35, 240),
      TCODColor(10, 200, 130)};  // random corner colors
  static int dir_r[4] = {1, -1, 1, 1}, dir_g[4] = {1, -1, -1, 1}, dir_b[4] = {1, 1, 1, -1};
  if (first) {
    sampleConsole.clear();
  }
  // ==== slightly modify the corner colors ====
  for (int c = 0; c < 4; c++) {
    // move each corner color
    int component = TCODRandom::getInstance()->getInt(0, 2);
    switch (component) {
      case 0:
        cols[c].r += 5 * dir_r[c];
        if (cols[c].r == 255)
          dir_r[c] = -1;
        else if (cols[c].r == 0)
          dir_r[c] = 1;
        break;
      case 1:
        cols[c].g += 5 * dir_g[c];
        if (cols[c].g == 255)
          dir_g[c] = -1;
        else if (cols[c].g == 0)
          dir_g[c] = 1;
        break;
      case 2:
        cols[c].b += 5 * dir_b[c];
        if (cols[c].b == 255)
          dir_b[c] = -1;
        else if (cols[c].b == 0)
          dir_b[c] = 1;
        break;
    }
  }

  // ==== scan the whole screen, interpolating corner colors ====
  for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
    float x_coef = (float)(x) / (SAMPLE_SCREEN_WIDTH - 1);
    // get the current column top and bottom colors
    TCODColor top = TCODColor::lerp(cols[TOPLEFT], cols[TOPRIGHT], x_coef);
    TCODColor bottom = TCODColor::lerp(cols[BOTTOMLEFT], cols[BOTTOMRIGHT], x_coef);
    for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
      float y_coef = (float)(y) / (SAMPLE_SCREEN_HEIGHT - 1);
      // get the current cell color
      TCODColor curColor = TCODColor::lerp(top, bottom, y_coef);
      sampleConsole.setCharBackground(x, y, curColor, TCOD_BKGND_SET);
    }
  }

  // ==== print the text with a random color ====
  // get the background color at the text position
  TCODColor textColor = sampleConsole.getCharBackground(SAMPLE_SCREEN_WIDTH / 2, 5);
  // and invert it
  textColor.r = 255 - textColor.r;
  textColor.g = 255 - textColor.g;
  textColor.b = 255 - textColor.b;
  // put random text (for performance tests)
  for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
    for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
      int c;
      TCODColor col = sampleConsole.getCharBackground(x, y);
      col = TCODColor::lerp(col, TCODColor::black, 0.5f);
      // use colored character 255 on first and last lines
      if (y == 0 || y == SAMPLE_SCREEN_HEIGHT - 1) {
        c = 255;
      } else {
        c = TCODRandom::getInstance()->getInt('a', 'z');
      }

      sampleConsole.setDefaultForeground(col);
      sampleConsole.putChar(x, y, c, TCOD_BKGND_NONE);
    }
  }
  sampleConsole.setDefaultForeground(textColor);
  // the background behind the text is slightly darkened using the BKGND_MULTIPLY flag
  sampleConsole.setDefaultBackground(TCODColor::grey);
  sampleConsole.printRectEx(
      SAMPLE_SCREEN_WIDTH / 2,
      5,
      SAMPLE_SCREEN_WIDTH - 2,
      SAMPLE_SCREEN_HEIGHT - 1,
      TCOD_BKGND_MULTIPLY,
      TCOD_CENTER,
      "The Doryen library uses 24 bits colors, for both background and foreground.");
}

// ***************************
// offscreen console sample
// ***************************
void render_offscreen(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  static TCODConsole secondary(SAMPLE_SCREEN_WIDTH / 2, SAMPLE_SCREEN_HEIGHT / 2);  // second screen
  static TCODConsole screenshot(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT);         // second screen
  static bool init = false;  // draw the secondary screen only the first time
  static int counter;
  static int x = 0, y = 0;          // secondary screen position
  static int x_dir = 1, y_dir = 1;  // movement direction
  if (!init) {
    init = true;
    secondary.printFrame(
        0, 0, SAMPLE_SCREEN_WIDTH / 2, SAMPLE_SCREEN_HEIGHT / 2, false, TCOD_BKGND_SET, "Offscreen console");
    secondary.printRectEx(
        SAMPLE_SCREEN_WIDTH / 4,
        2,
        SAMPLE_SCREEN_WIDTH / 2 - 2,
        SAMPLE_SCREEN_HEIGHT / 2,
        TCOD_BKGND_NONE,
        TCOD_CENTER,
        "You can render to an offscreen console and blit in on another one, simulating alpha transparency.");
  }
  if (first) {
    counter = SDL_GetTicks();
    // get a "screenshot" of the current sample screen
    TCODConsole::blit(&sampleConsole, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, &screenshot, 0, 0);
  }
  if (SDL_TICKS_PASSED(SDL_GetTicks(), counter + 1000)) {  // Once every second.
    counter = SDL_GetTicks();
    // move the secondary screen every 2 seconds
    x += x_dir;
    y += y_dir;
    if (x == SAMPLE_SCREEN_WIDTH / 2 + 5)
      x_dir = -1;
    else if (x == -5)
      x_dir = 1;
    if (y == SAMPLE_SCREEN_HEIGHT / 2 + 5)
      y_dir = -1;
    else if (y == -5)
      y_dir = 1;
  }
  // restore the initial screen
  TCODConsole::blit(&screenshot, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, &sampleConsole, 0, 0);
  // blit the overlapping screen
  TCODConsole::blit(
      &secondary, 0, 0, SAMPLE_SCREEN_WIDTH / 2, SAMPLE_SCREEN_HEIGHT / 2, &sampleConsole, x, y, 1.0f, 0.75f);
}

// ***************************
// line drawing sample
// ***************************
static int bkFlag = TCOD_BKGND_SET;  // current blending mode
class LineListener : public TCODLineListener {
 public:
  bool putPoint(int x, int y) {
    if (x >= 0 && y >= 0 && x < SAMPLE_SCREEN_WIDTH && y < SAMPLE_SCREEN_HEIGHT) {
      sampleConsole.setCharBackground(x, y, TCODColor::lightBlue, (TCOD_bkgnd_flag_t)bkFlag);
    }
    return true;
  }
};
void render_lines(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  static TCODConsole bk(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT);  // colored background
  static bool init = false;
  static const std::vector<std::string> flagNames = {
      "TCOD_BKGND_NONE",
      "TCOD_BKGND_SET",
      "TCOD_BKGND_MULTIPLY",
      "TCOD_BKGND_LIGHTEN",
      "TCOD_BKGND_DARKEN",
      "TCOD_BKGND_SCREEN",
      "TCOD_BKGND_COLOR_DODGE",
      "TCOD_BKGND_COLOR_BURN",
      "TCOD_BKGND_ADD",
      "TCOD_BKGND_ADDALPHA",
      "TCOD_BKGND_BURN",
      "TCOD_BKGND_OVERLAY",
      "TCOD_BKGND_ALPHA"};
  if (key->vk == TCODK_ENTER || key->vk == TCODK_KPENTER) {
    // switch to the next blending mode
    bkFlag++;
    if ((bkFlag & 0xff) > TCOD_BKGND_ALPH) bkFlag = TCOD_BKGND_NONE;
  }
  if ((bkFlag & 0xff) == TCOD_BKGND_ALPH) {
    // for the alpha mode, update alpha every frame
    float alpha = (1.0f + cosf(TCODSystem::getElapsedSeconds() * 2)) / 2.0f;
    bkFlag = TCOD_BKGND_ALPHA(alpha);
  } else if ((bkFlag & 0xff) == TCOD_BKGND_ADDA) {
    // for the add alpha mode, update alpha every frame
    float alpha = (1.0f + cosf(TCODSystem::getElapsedSeconds() * 2)) / 2.0f;
    bkFlag = TCOD_BKGND_ADDALPHA(alpha);
  }
  if (!init) {
    // initialize the colored background
    for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
      for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
        TCODColor col;
        col.r = (uint8_t)(x * 255 / (SAMPLE_SCREEN_WIDTH - 1));
        col.g = (uint8_t)((x + y) * 255 / (SAMPLE_SCREEN_WIDTH - 1 + SAMPLE_SCREEN_HEIGHT - 1));
        col.b = (uint8_t)(y * 255 / (SAMPLE_SCREEN_HEIGHT - 1));
        bk.setCharBackground(x, y, col, TCOD_BKGND_SET);
      }
    }
    init = true;
  }
  if (first) {
    sampleConsole.setDefaultForeground(TCODColor::white);
  }
  // blit the background
  TCODConsole::blit(&bk, 0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT, &sampleConsole, 0, 0);
  // render the gradient
  int rect_y = (int)((SAMPLE_SCREEN_HEIGHT - 2) * ((1.0f + cosf(TCODSystem::getElapsedSeconds())) / 2.0f));
  for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
    TCODColor col;
    col.r = (uint8_t)(x * 255 / SAMPLE_SCREEN_WIDTH);
    col.g = (uint8_t)(x * 255 / SAMPLE_SCREEN_WIDTH);
    col.b = (uint8_t)(x * 255 / SAMPLE_SCREEN_WIDTH);
    sampleConsole.setCharBackground(x, rect_y, col, (TCOD_bkgnd_flag_t)bkFlag);
    sampleConsole.setCharBackground(x, rect_y + 1, col, (TCOD_bkgnd_flag_t)bkFlag);
    sampleConsole.setCharBackground(x, rect_y + 2, col, (TCOD_bkgnd_flag_t)bkFlag);
  }
  // calculate the segment ends
  float angle = TCODSystem::getElapsedSeconds() * 2.0f;
  float cosAngle = cosf(angle);
  float sinAngle = sinf(angle);
  int xo = (int)(SAMPLE_SCREEN_WIDTH / 2 * (1 + cosAngle));
  int yo = (int)(SAMPLE_SCREEN_HEIGHT / 2 + sinAngle * SAMPLE_SCREEN_WIDTH / 2);
  int xd = (int)(SAMPLE_SCREEN_WIDTH / 2 * (1 - cosAngle));
  int yd = (int)(SAMPLE_SCREEN_HEIGHT / 2 - sinAngle * SAMPLE_SCREEN_WIDTH / 2);

  // render the line
  LineListener listener;
  TCODLine::line(xo, yo, xd, yd, &listener);
  // print the current flag
  sampleConsole.print(2, 2, flagNames[bkFlag & 0xff] + " (ENTER to change)");
}

// ***************************
// noise sample
// ***************************
void render_noise(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  enum {
    PERLIN,
    SIMPLEX,
    WAVELET,
    FBM_PERLIN,
    TURBULENCE_PERLIN,
    FBM_SIMPLEX,
    TURBULENCE_SIMPLEX,
    FBM_WAVELET,
    TURBULENCE_WAVELET
  };  // which function we render
  static const std::vector<std::string> funcName = {
      "1 : perlin noise       ",
      "2 : simplex noise      ",
      "3 : wavelet noise      ",
      "4 : perlin fbm         ",
      "5 : perlin turbulence  ",
      "6 : simplex fbm        ",
      "7 : simplex turbulence ",
      "8 : wavelet fbm        ",
      "9 : wavelet turbulence ",
  };
  static int func = PERLIN;
  static TCODNoise* noise = NULL;
  static float octaves = 4.0f;
  static float hurst = TCOD_NOISE_DEFAULT_HURST;
  static float lacunarity = TCOD_NOISE_DEFAULT_LACUNARITY;
  static TCODImage* img = NULL;
  static float zoom = 3.0f;
  if (!noise) {
    noise = new TCODNoise(2, hurst, lacunarity);
    img = new TCODImage(SAMPLE_SCREEN_WIDTH * 2, SAMPLE_SCREEN_HEIGHT * 2);
  }

  // texture animation
  const float dx = SDL_GetTicks() * 0.0005f;
  const float dy = dx;
  // render the 2d noise function
  for (int y = 0; y < 2 * SAMPLE_SCREEN_HEIGHT; y++) {
    for (int x = 0; x < 2 * SAMPLE_SCREEN_WIDTH; x++) {
      float f[2];
      f[0] = zoom * x / (2 * SAMPLE_SCREEN_WIDTH) + dx;
      f[1] = zoom * y / (2 * SAMPLE_SCREEN_HEIGHT) + dy;
      float value = 0.0f;
      switch (func) {
        case PERLIN:
          value = noise->get(f, TCOD_NOISE_PERLIN);
          break;
        case SIMPLEX:
          value = noise->get(f, TCOD_NOISE_SIMPLEX);
          break;
        case WAVELET:
          value = noise->get(f, TCOD_NOISE_WAVELET);
          break;
        case FBM_PERLIN:
          value = noise->getFbm(f, octaves, TCOD_NOISE_PERLIN);
          break;
        case TURBULENCE_PERLIN:
          value = noise->getTurbulence(f, octaves, TCOD_NOISE_PERLIN);
          break;
        case FBM_SIMPLEX:
          value = noise->getFbm(f, octaves, TCOD_NOISE_SIMPLEX);
          break;
        case TURBULENCE_SIMPLEX:
          value = noise->getTurbulence(f, octaves, TCOD_NOISE_SIMPLEX);
          break;
        case FBM_WAVELET:
          value = noise->getFbm(f, octaves, TCOD_NOISE_WAVELET);
          break;
        case TURBULENCE_WAVELET:
          value = noise->getTurbulence(f, octaves, TCOD_NOISE_WAVELET);
          break;
      }
      uint8_t c = (uint8_t)((value + 1.0f) / 2.0f * 255);
      // use a bluish color
      TCODColor col((uint8_t)(c / 2), (uint8_t)(c / 2), c);
      img->putPixel(x, y, col);
    }
  }
  // blit the noise image on the console with subcell resolution
  img->blit2x(&sampleConsole, 0, 0);
  // draw a transparent rectangle
  sampleConsole.setDefaultBackground(TCODColor::grey);
  sampleConsole.rect(2, 2, 23, (func <= WAVELET ? 10 : 13), false, TCOD_BKGND_MULTIPLY);
  for (int y = 2; y < 2 + (func <= WAVELET ? 10 : 13); y++) {
    for (int x = 2; x < 2 + 23; x++) {
      TCODColor col = sampleConsole.getCharForeground(x, y);
      col = col * TCODColor::grey;
      sampleConsole.setCharForeground(x, y, col);
    }
  }

  // draw the text
  for (int curfunc = PERLIN; curfunc <= TURBULENCE_WAVELET; curfunc++) {
    if (curfunc == func) {
      sampleConsole.setDefaultForeground(TCODColor::white);
      sampleConsole.setDefaultBackground(TCODColor::lightBlue);
      sampleConsole.print(2, 2 + curfunc, funcName[curfunc], TCOD_LEFT, TCOD_BKGND_SET);
    } else {
      sampleConsole.setDefaultForeground(TCODColor::grey);
      sampleConsole.print(2, 2 + curfunc, funcName[curfunc]);
    }
  }
  // draw parameters
  sampleConsole.setDefaultForeground(TCODColor::white);
  sampleConsole.printf(2, 11, "Y/H : zoom (%2.1f)", zoom);
  if (func > WAVELET) {
    sampleConsole.printf(2, 12, "E/D : hurst (%2.1f)", hurst);
    sampleConsole.printf(2, 13, "R/F : lacunarity (%2.1f)", lacunarity);
    sampleConsole.printf(2, 14, "T/G : octaves (%2.1f)", octaves);
  }
  // handle keypress
  if (key->vk == TCODK_NONE) return;
  if (key->c >= '1' && key->c <= '9') {
    // change the noise function
    func = key->c - '1';
  } else if (key->c == 'E' || key->c == 'e') {
    // increase hurst
    hurst += 0.1f;
    delete noise;
    noise = new TCODNoise(2, hurst, lacunarity);
  } else if (key->c == 'D' || key->c == 'd') {
    // decrease hurst
    hurst -= 0.1f;
    delete noise;
    noise = new TCODNoise(2, hurst, lacunarity);
  } else if (key->c == 'R' || key->c == 'r') {
    // increase lacunarity
    lacunarity += 0.5f;
    delete noise;
    noise = new TCODNoise(2, hurst, lacunarity);
  } else if (key->c == 'F' || key->c == 'f') {
    // decrease lacunarity
    lacunarity -= 0.5f;
    delete noise;
    noise = new TCODNoise(2, hurst, lacunarity);
  } else if (key->c == 'T' || key->c == 't') {
    // increase octaves
    octaves += 0.5f;
  } else if (key->c == 'G' || key->c == 'g') {
    // decrease octaves
    octaves -= 0.5f;
  } else if (key->c == 'Y' || key->c == 'y') {
    // increase zoom
    zoom += 0.2f;
  } else if (key->c == 'H' || key->c == 'h') {
    // decrease zoom
    zoom -= 0.2f;
  }
}

// ***************************
// fov sample
// ***************************
void render_fov(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  // clang-format off
  static const char* s_map[] = {
      "##############################################",
      "#######################      #################",
      "#####################    #     ###############",
      "######################  ###        ###########",
      "##################      #####             ####",
      "################       ########    ###### ####",
      "###############      #################### ####",
      "################    ######                  ##",
      "########   #######  ######   #     #     #  ##",
      "########   ######      ###                  ##",
      "########                                    ##",
      "####       ######      ###   #     #     #  ##",
      "#### ###   ########## ####                  ##",
      "#### ###   ##########   ###########=##########",
      "#### ##################   #####          #####",
      "#### ###             #### #####          #####",
      "####           #     ####                #####",
      "########       #     #### #####          #####",
      "########       #####      ####################",
      "##############################################",
  };
  // clang-format on
#define TORCH_RADIUS 10.0f
#define SQUARED_TORCH_RADIUS (TORCH_RADIUS * TORCH_RADIUS)
  static int px = 20, py = 10;      // player position
  static bool recomputeFov = true;  // the player moved. must recompute fov
  static bool torch = false;        // torch fx on ?
  static TCODMap* map = NULL;
  static TCODColor darkWall(0, 0, 100);
  static TCODColor lightWall(130, 110, 50);
  static TCODColor darkGround(50, 50, 150);
  static TCODColor lightGround(200, 180, 50);
  static TCODNoise* noise = NULL;
  static bool light_walls = true;
  static int algonum = 0;
  static const std::vector<std::string> algo_names = {
      "BASIC               ",
      "DIAMOND             ",
      "SHADOW              ",
      "PERMISSIVE0         ",
      "PERMISSIVE1         ",
      "PERMISSIVE2         ",
      "PERMISSIVE3         ",
      "PERMISSIVE4         ",
      "PERMISSIVE5         ",
      "PERMISSIVE6         ",
      "PERMISSIVE7         ",
      "PERMISSIVE8         ",
      "RESTRICTIVE         ",
      "SYMMETRIC_SHADOWCAST",
  };
  static float torch_x = 0.0f;  // torch light position in the perlin noise
  if (!map) {
    // initialize the map for the fov toolkit
    map = new TCODMap(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT);
    for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
      for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
        if (s_map[y][x] == ' ')
          map->setProperties(x, y, true, true);  // ground
        else if (s_map[y][x] == '=')
          map->setProperties(x, y, true, false);  // window
      }
    }
    // 1d noise used for the torch flickering
    noise = new TCODNoise(1);
  }
  if (first) {
    // we draw the foreground only the first time.
    // during the player movement, only the @ is redrawn.
    // the rest impacts only the background color
    // draw the help text & player @
    sampleConsole.clear();
    sampleConsole.setDefaultForeground(TCODColor::white);
    sampleConsole.printf(
        1,
        0,
        "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s",
        torch ? "on " : "off",
        light_walls ? "on " : "off",
        algo_names[algonum].c_str());
    sampleConsole.setDefaultForeground(TCODColor::black);
    sampleConsole.putChar(px, py, '@', TCOD_BKGND_NONE);
    // draw windows
    for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
      for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
        if (s_map[y][x] == '=') {
          sampleConsole.putChar(x, y, TCOD_CHAR_DHLINE, TCOD_BKGND_NONE);
        }
      }
    }
  }
  if (recomputeFov) {
    // calculate the field of view from the player position
    recomputeFov = false;
    map->computeFov(px, py, torch ? (int)(TORCH_RADIUS) : 0, light_walls, (TCOD_fov_algorithm_t)algonum);
  }
  // torch position & intensity variation
  float dx = 0.0f, dy = 0.0f, di = 0.0f;
  if (torch) {
    // slightly change the perlin noise parameter
    torch_x += 0.2f;
    // randomize the light position between -1.5 and 1.5
    float tdx = torch_x + 20.0f;
    dx = noise->get(&tdx) * 1.5f;
    tdx += 30.0f;
    dy = noise->get(&tdx) * 1.5f;
    // randomize the light intensity between -0.2 and 0.2
    di = 0.2f * noise->get(&torch_x);
  }
  // draw the dungeon
  for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
    for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
      bool visible = map->isInFov(x, y);
      bool wall = s_map[y][x] == '#';
      if (!visible) {
        sampleConsole.setCharBackground(x, y, wall ? darkWall : darkGround, TCOD_BKGND_SET);
      } else {
        TCODColor light;
        if (!torch) {
          light = wall ? lightWall : lightGround;
        } else {
          // torch flickering fx
          TCODColor base = (wall ? darkWall : darkGround);
          light = (wall ? lightWall : lightGround);
          // cell distance to torch (squared)
          float r = (float)((x - px + dx) * (x - px + dx) + (y - py + dy) * (y - py + dy));
          if (r < SQUARED_TORCH_RADIUS) {
            // l = 1.0 at player position, 0.0 at a radius of 10 cells
            float l = (SQUARED_TORCH_RADIUS - r) / SQUARED_TORCH_RADIUS + di;
            l = CLAMP(0.0f, 1.0f, l);
            // interpolate the color
            base = TCODColor::lerp(base, light, l);
          }
          light = base;
        }
        sampleConsole.setCharBackground(x, y, light, TCOD_BKGND_SET);
      }
    }
  }
  if (key->c == 'I' || key->c == 'i') {
    // player move north
    if (s_map[py - 1][px] == ' ') {
      sampleConsole.putChar(px, py, ' ', TCOD_BKGND_NONE);
      py--;
      sampleConsole.putChar(px, py, '@', TCOD_BKGND_NONE);
      recomputeFov = true;
    }
  } else if (key->c == 'K' || key->c == 'k') {
    // player move south
    if (s_map[py + 1][px] == ' ') {
      sampleConsole.putChar(px, py, ' ', TCOD_BKGND_NONE);
      py++;
      sampleConsole.putChar(px, py, '@', TCOD_BKGND_NONE);
      recomputeFov = true;
    }
  } else if (key->c == 'J' || key->c == 'j') {
    // player move west
    if (s_map[py][px - 1] == ' ') {
      sampleConsole.putChar(px, py, ' ', TCOD_BKGND_NONE);
      px--;
      sampleConsole.putChar(px, py, '@', TCOD_BKGND_NONE);
      recomputeFov = true;
    }
  } else if (key->c == 'L' || key->c == 'l') {
    // player move east
    if (s_map[py][px + 1] == ' ') {
      sampleConsole.putChar(px, py, ' ', TCOD_BKGND_NONE);
      px++;
      sampleConsole.putChar(px, py, '@', TCOD_BKGND_NONE);
      recomputeFov = true;
    }
  } else if (key->c == 'T' || key->c == 't') {
    // enable/disable the torch fx
    torch = !torch;
    sampleConsole.setDefaultForeground(TCODColor::white);
    sampleConsole.printf(
        1,
        0,
        "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s",
        torch ? "on " : "off",
        light_walls ? "on " : "off",
        algo_names[algonum].c_str());
    sampleConsole.setDefaultForeground(TCODColor::black);
  } else if (key->c == 'W' || key->c == 'w') {
    light_walls = !light_walls;
    sampleConsole.setDefaultForeground(TCODColor::white);
    sampleConsole.printf(
        1,
        0,
        "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s",
        torch ? "on " : "off",
        light_walls ? "on " : "off",
        algo_names[algonum].c_str());
    sampleConsole.setDefaultForeground(TCODColor::black);
    recomputeFov = true;
  } else if (key->c == '+' || key->c == '-') {
    algonum += key->c == '+' ? 1 : -1;
    algonum = (algonum + NB_FOV_ALGORITHMS) % NB_FOV_ALGORITHMS;
    sampleConsole.setDefaultForeground(TCODColor::white);
    sampleConsole.printf(
        1,
        0,
        "IJKL : move around\nT : torch fx %s\nW : light walls %s\n+-: algo %s",
        torch ? "on " : "off",
        light_walls ? "on " : "off",
        algo_names[algonum].c_str());
    sampleConsole.setDefaultForeground(TCODColor::black);
    recomputeFov = true;
  }
}

// ***************************
// image sample
// ***************************
void render_image(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  static TCODImage *img = NULL, *circle = NULL;
  static TCODColor blue(0, 0, 255);
  static TCODColor green(0, 255, 0);
  if (img == NULL) {
    img = new TCODImage("data/img/skull.png");
    img->setKeyColor(TCODColor::black);
    circle = new TCODImage("data/img/circle.png");
  }
  sampleConsole.setDefaultBackground(TCODColor::black);
  sampleConsole.clear();
  float x = SAMPLE_SCREEN_WIDTH / 2 + cosf(TCODSystem::getElapsedSeconds()) * 10.0f;
  float y = (float)(SAMPLE_SCREEN_HEIGHT / 2);
  float scale_x = 0.2f + 1.8f * (1.0f + cosf(TCODSystem::getElapsedSeconds() / 2)) / 2.0f;
  float scale_y = scale_x;
  float angle = TCODSystem::getElapsedSeconds();
  long elapsed = TCODSystem::getElapsedMilli() / 2000;
  if (elapsed & 1) {
    // split the color channels of circle.png
    // the red channel
    sampleConsole.setDefaultBackground(TCODColor::red);
    sampleConsole.rect(0, 3, 15, 15, false, TCOD_BKGND_SET);
    circle->blitRect(&sampleConsole, 0, 3, -1, -1, TCOD_BKGND_MULTIPLY);
    // the green channel
    sampleConsole.setDefaultBackground(green);
    sampleConsole.rect(15, 3, 15, 15, false, TCOD_BKGND_SET);
    circle->blitRect(&sampleConsole, 15, 3, -1, -1, TCOD_BKGND_MULTIPLY);
    // the blue channel
    sampleConsole.setDefaultBackground(blue);
    sampleConsole.rect(30, 3, 15, 15, false, TCOD_BKGND_SET);
    circle->blitRect(&sampleConsole, 30, 3, -1, -1, TCOD_BKGND_MULTIPLY);
  } else {
    // render circle.png with normal blitting
    circle->blitRect(&sampleConsole, 0, 3, -1, -1, TCOD_BKGND_SET);
    circle->blitRect(&sampleConsole, 15, 3, -1, -1, TCOD_BKGND_SET);
    circle->blitRect(&sampleConsole, 30, 3, -1, -1, TCOD_BKGND_SET);
  }
  img->blit(&sampleConsole, x, y, TCOD_BKGND_SET, scale_x, scale_y, angle);
}

// ***************************
// mouse sample
// ***************************/
void render_mouse(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  static bool left_button = false, right_button = false, middle_button = false;

  if (first) {
    sampleConsole.setDefaultBackground(TCODColor::grey);
    sampleConsole.setDefaultForeground(TCODColor::lightYellow);
    SDL_WarpMouseInWindow(NULL, 320, 200);
    SDL_ShowCursor(1);
  }

  sampleConsole.clear();
  if (mouse->lbutton_pressed) left_button = !left_button;
  if (mouse->rbutton_pressed) right_button = !right_button;
  if (mouse->mbutton_pressed) middle_button = !middle_button;
  sampleConsole.printf(
      1,
      1,
      "%s\n"
      "Mouse position : %4dx%4d %s\n"
      "Mouse cell     : %4dx%4d\n"
      "Mouse movement : %4dx%4d\n"
      "Left button    : %s (toggle %s)\n"
      "Right button   : %s (toggle %s)\n"
      "Middle button  : %s (toggle %s)\n"
      "Wheel          : %s\n",
      TCODConsole::isActive() ? "" : "APPLICATION INACTIVE",
      mouse->x,
      mouse->y,
      TCODConsole::hasMouseFocus() ? "" : "OUT OF FOCUS",
      mouse->cx,
      mouse->cy,
      mouse->dx,
      mouse->dy,
      mouse->lbutton ? " ON" : "OFF",
      left_button ? " ON" : "OFF",
      mouse->rbutton ? " ON" : "OFF",
      right_button ? " ON" : "OFF",
      mouse->mbutton ? " ON" : "OFF",
      middle_button ? " ON" : "OFF",
      mouse->wheel_up ? "UP" : (mouse->wheel_down ? "DOWN" : ""));
  sampleConsole.printf(1, 10, "1 : Hide cursor\n2 : Show cursor");
  if (key->c == '1') {
    SDL_ShowCursor(0);
  } else if (key->c == '2') {
    SDL_ShowCursor(1);
  }
}

// ***************************
// path sample
// ***************************
void render_path(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  // clang-format off
  static const char* s_map[] = {
      "##############################################",
      "#######################      #################",
      "#####################    #     ###############",
      "######################  ###        ###########",
      "##################      #####             ####",
      "################       ########    ###### ####",
      "###############      #################### ####",
      "################    ######                  ##",
      "########   #######  ######   #     #     #  ##",
      "########   ######      ###                  ##",
      "########                                    ##",
      "####       ######      ###   #     #     #  ##",
      "#### ###   ########## ####                  ##",
      "#### ###   ##########   ###########=##########",
      "#### ##################   #####          #####",
      "#### ###             #### #####          #####",
      "####           #     ####                #####",
      "########       #     #### #####          #####",
      "########       #####      ####################",
      "##############################################",
  };
  // clang-format on
#define TORCH_RADIUS 10.0f
#define SQUARED_TORCH_RADIUS (TORCH_RADIUS * TORCH_RADIUS)
  static int px = 20, py = 10;  // player position
  static int dx = 24, dy = 1;   // destination
  static TCODMap* map = NULL;
  static TCODColor darkWall(0, 0, 100);
  static TCODColor darkGround(50, 50, 150);
  static TCODColor lightGround(200, 180, 50);
  static TCODPath* path = NULL;
  static bool usingAstar = true;
  static float dijkstraDist = 0;
  static TCODDijkstra* dijkstra = NULL;
  static bool recalculatePath = false;
  static float busy;
  static int oldChar = ' ';
  int mx, my;
  if (!map) {
    // initialize the map for the fov toolkit
    map = new TCODMap(SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT);
    for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
      for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
        if (s_map[y][x] == ' ')
          map->setProperties(x, y, true, true);  // ground
        else if (s_map[y][x] == '=')
          map->setProperties(x, y, true, false);  // window
      }
    }
    path = new TCODPath(map);
    dijkstra = new TCODDijkstra(map);
  }
  if (first) {
    // we draw the foreground only the first time.
    // during the player movement, only the @ is redrawn.
    // the rest impacts only the background color
    // draw the help text & player @
    sampleConsole.clear();
    sampleConsole.setDefaultForeground(TCODColor::white);
    sampleConsole.putChar(dx, dy, '+', TCOD_BKGND_NONE);
    sampleConsole.putChar(px, py, '@', TCOD_BKGND_NONE);
    sampleConsole.print(1, 1, std::string("IJKL / mouse :\nmove destination\nTAB : A*/dijkstra"));
    sampleConsole.print(1, 4, std::string("Using : A*"));
    // draw windows
    for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
      for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
        if (s_map[y][x] == '=') {
          sampleConsole.putChar(x, y, TCOD_CHAR_DHLINE, TCOD_BKGND_NONE);
        }
      }
    }
    recalculatePath = true;
  }
  if (recalculatePath) {
    if (usingAstar) {
      path->compute(px, py, dx, dy);
    } else {
      dijkstraDist = 0.0f;
      // compute the distance grid
      dijkstra->compute(px, py);
      // get the maximum distance (needed for ground shading only)
      for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
        for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
          float d = dijkstra->getDistance(x, y);
          if (d > dijkstraDist) dijkstraDist = d;
        }
      }
      // compute the path
      dijkstra->setPath(dx, dy);
    }
    recalculatePath = false;
    busy = 0.2f;
  }
  // draw the dungeon
  for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
    for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
      bool wall = s_map[y][x] == '#';
      sampleConsole.setCharBackground(x, y, wall ? darkWall : darkGround, TCOD_BKGND_SET);
    }
  }
  // draw the path
  if (usingAstar) {
    for (int i = 0; i < path->size(); i++) {
      int x, y;
      path->get(i, &x, &y);
      sampleConsole.setCharBackground(x, y, lightGround, TCOD_BKGND_SET);
    }
  } else {
    for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
      for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
        bool wall = s_map[y][x] == '#';
        if (!wall) {
          float d = dijkstra->getDistance(x, y);
          sampleConsole.setCharBackground(
              x, y, TCODColor::lerp(lightGround, darkGround, 0.9f * d / dijkstraDist), TCOD_BKGND_SET);
        }
      }
    }
    for (int i = 0; i < dijkstra->size(); i++) {
      int x, y;
      dijkstra->get(i, &x, &y);
      sampleConsole.setCharBackground(x, y, lightGround, TCOD_BKGND_SET);
    }
  }
  // move the creature
  busy -= TCODSystem::getLastFrameLength();
  if (busy <= 0.0f) {
    busy = 0.2f;
    if (usingAstar) {
      if (!path->isEmpty()) {
        sampleConsole.putChar(px, py, ' ', TCOD_BKGND_NONE);
        path->walk(&px, &py, true);
        sampleConsole.putChar(px, py, '@', TCOD_BKGND_NONE);
      }
    } else {
      if (!dijkstra->isEmpty()) {
        sampleConsole.putChar(px, py, ' ', TCOD_BKGND_NONE);
        dijkstra->walk(&px, &py);
        sampleConsole.putChar(px, py, '@', TCOD_BKGND_NONE);
        recalculatePath = true;
      }
    }
  }
  if ((key->c == 'I' || key->c == 'i') && dy > 0) {
    // destination move north
    sampleConsole.putChar(dx, dy, oldChar, TCOD_BKGND_NONE);
    dy--;
    oldChar = sampleConsole.getChar(dx, dy);
    sampleConsole.putChar(dx, dy, '+', TCOD_BKGND_NONE);
    if (s_map[dy][dx] == ' ') {
      recalculatePath = true;
    }
  } else if ((key->c == 'K' || key->c == 'k') && dy < SAMPLE_SCREEN_HEIGHT - 1) {
    // destination move south
    sampleConsole.putChar(dx, dy, oldChar, TCOD_BKGND_NONE);
    dy++;
    oldChar = sampleConsole.getChar(dx, dy);
    sampleConsole.putChar(dx, dy, '+', TCOD_BKGND_NONE);
    if (s_map[dy][dx] == ' ') {
      recalculatePath = true;
    }
  } else if ((key->c == 'J' || key->c == 'j') && dx > 0) {
    // destination move west
    sampleConsole.putChar(dx, dy, oldChar, TCOD_BKGND_NONE);
    dx--;
    oldChar = sampleConsole.getChar(dx, dy);
    sampleConsole.putChar(dx, dy, '+', TCOD_BKGND_NONE);
    if (s_map[dy][dx] == ' ') {
      recalculatePath = true;
    }
  } else if ((key->c == 'L' || key->c == 'l') && dx < SAMPLE_SCREEN_WIDTH - 1) {
    // destination move east
    sampleConsole.putChar(dx, dy, oldChar, TCOD_BKGND_NONE);
    dx++;
    oldChar = sampleConsole.getChar(dx, dy);
    sampleConsole.putChar(dx, dy, '+', TCOD_BKGND_NONE);
    if (s_map[dy][dx] == ' ') {
      recalculatePath = true;
    }
  } else if (key->vk == TCODK_TAB) {
    usingAstar = !usingAstar;
    if (usingAstar)
      sampleConsole.print(1, 4, std::string("Using : A*      "));
    else
      sampleConsole.print(1, 4, std::string("Using : Dijkstra"));
    recalculatePath = true;
  }
  mx = mouse->cx - SAMPLE_SCREEN_X;
  my = mouse->cy - SAMPLE_SCREEN_Y;
  if (mx >= 0 && mx < SAMPLE_SCREEN_WIDTH && my >= 0 && my < SAMPLE_SCREEN_HEIGHT && (dx != mx || dy != my)) {
    sampleConsole.putChar(dx, dy, oldChar, TCOD_BKGND_NONE);
    dx = mx;
    dy = my;
    oldChar = sampleConsole.getChar(dx, dy);
    sampleConsole.putChar(dx, dy, '+', TCOD_BKGND_NONE);
    if (s_map[dy][dx] == ' ') {
      recalculatePath = true;
    }
  }
}

// ***************************
// bsp sample
// ***************************

static int bspDepth = 8;
static int minRoomSize = 4;
static bool randomRoom = false;  // a room fills a random part of the node or the maximum available space ?
static bool roomWalls = true;    // if true, there is always a wall on north & west side of a room
typedef char map_t[SAMPLE_SCREEN_WIDTH][SAMPLE_SCREEN_HEIGHT];

// draw a vertical line
void vline(map_t* map, int x, int y1, int y2) {
  int y = y1;
  int dy = (y1 > y2 ? -1 : 1);
  (*map)[x][y] = ' ';
  if (y1 == y2) return;
  do {
    y += dy;
    (*map)[x][y] = ' ';
  } while (y != y2);
}

// draw a vertical line up until we reach an empty space
void vline_up(map_t* map, int x, int y) {
  while (y >= 0 && (*map)[x][y] != ' ') {
    (*map)[x][y] = ' ';
    y--;
  }
}

// draw a vertical line down until we reach an empty space
void vline_down(map_t* map, int x, int y) {
  while (y < SAMPLE_SCREEN_HEIGHT && (*map)[x][y] != ' ') {
    (*map)[x][y] = ' ';
    y++;
  }
}

// draw a horizontal line
void hline(map_t* map, int x1, int y, int x2) {
  int x = x1;
  int dx = (x1 > x2 ? -1 : 1);
  (*map)[x][y] = ' ';
  if (x1 == x2) return;
  do {
    x += dx;
    (*map)[x][y] = ' ';
  } while (x != x2);
}

// draw a horizontal line left until we reach an empty space
void hline_left(map_t* map, int x, int y) {
  while (x >= 0 && (*map)[x][y] != ' ') {
    (*map)[x][y] = ' ';
    x--;
  }
}

// draw a horizontal line right until we reach an empty space
void hline_right(map_t* map, int x, int y) {
  while (x < SAMPLE_SCREEN_WIDTH && (*map)[x][y] != ' ') {
    (*map)[x][y] = ' ';
    x++;
  }
}

// the class building the dungeon from the bsp nodes
//#include <stdio.h>
class BspListener : public ITCODBspCallback {
 public:
  bool visitNode(TCODBsp* node, void* userData) {
    map_t* map = (map_t*)userData;
    if (node->isLeaf()) {
      // calculate the room size
      int minx = node->x + 1;
      int maxx = node->x + node->w - 1;
      int miny = node->y + 1;
      int maxy = node->y + node->h - 1;
      if (!roomWalls) {
        if (minx > 1) minx--;
        if (miny > 1) miny--;
      }
      if (maxx == SAMPLE_SCREEN_WIDTH - 1) maxx--;
      if (maxy == SAMPLE_SCREEN_HEIGHT - 1) maxy--;
      if (randomRoom) {
        minx = TCODRandom::getInstance()->getInt(minx, maxx - minRoomSize + 1);
        miny = TCODRandom::getInstance()->getInt(miny, maxy - minRoomSize + 1);
        maxx = TCODRandom::getInstance()->getInt(minx + minRoomSize - 1, maxx);
        maxy = TCODRandom::getInstance()->getInt(miny + minRoomSize - 1, maxy);
      }
      // resize the node to fit the room
      // printf("node %dx%d %dx%d => room %dx%d
      // %dx%d\n",node->x,node->y,node->w,node->h,minx,miny,maxx-minx+1,maxy-miny+1);
      node->x = minx;
      node->y = miny;
      node->w = maxx - minx + 1;
      node->h = maxy - miny + 1;
      // dig the room
      for (int x = minx; x <= maxx; x++) {
        for (int y = miny; y <= maxy; y++) {
          (*map)[x][y] = ' ';
        }
      }
    } else {
      // printf("lvl %d %dx%d %dx%d\n",node->level, node->x,node->y,node->w,node->h);
      // resize the node to fit its sons
      TCODBsp* left = node->getLeft();
      TCODBsp* right = node->getRight();
      node->x = MIN(left->x, right->x);
      node->y = MIN(left->y, right->y);
      node->w = MAX(left->x + left->w, right->x + right->w) - node->x;
      node->h = MAX(left->y + left->h, right->y + right->h) - node->y;
      // create a corridor between the two lower nodes
      if (node->horizontal) {
        // vertical corridor
        if (left->x + left->w - 1 < right->x || right->x + right->w - 1 < left->x) {
          // no overlapping zone. we need a Z shaped corridor
          int x1 = TCODRandom::getInstance()->getInt(left->x, left->x + left->w - 1);
          int x2 = TCODRandom::getInstance()->getInt(right->x, right->x + right->w - 1);
          int y = TCODRandom::getInstance()->getInt(left->y + left->h, right->y);
          vline_up(map, x1, y - 1);
          hline(map, x1, y, x2);
          vline_down(map, x2, y + 1);
        } else {
          // straight vertical corridor
          int minx = MAX(left->x, right->x);
          int maxx = MIN(left->x + left->w - 1, right->x + right->w - 1);
          int x = TCODRandom::getInstance()->getInt(minx, maxx);
          vline_down(map, x, right->y);
          vline_up(map, x, right->y - 1);
        }
      } else {
        // horizontal corridor
        if (left->y + left->h - 1 < right->y || right->y + right->h - 1 < left->y) {
          // no overlapping zone. we need a Z shaped corridor
          int y1 = TCODRandom::getInstance()->getInt(left->y, left->y + left->h - 1);
          int y2 = TCODRandom::getInstance()->getInt(right->y, right->y + right->h - 1);
          int x = TCODRandom::getInstance()->getInt(left->x + left->w, right->x);
          hline_left(map, x - 1, y1);
          vline(map, x, y1, y2);
          hline_right(map, x + 1, y2);
        } else {
          // straight horizontal corridor
          int miny = MAX(left->y, right->y);
          int maxy = MIN(left->y + left->h - 1, right->y + right->h - 1);
          int y = TCODRandom::getInstance()->getInt(miny, maxy);
          hline_left(map, right->x - 1, y);
          hline_right(map, right->x, y);
        }
      }
    }
    return true;
  }
};
void render_bsp(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  static TCODBsp* bsp = NULL;
  static bool generate = true;
  static bool refresh = false;
  static map_t map;
  static TCODColor darkWall(0, 0, 100);
  static TCODColor darkGround(50, 50, 150);
  static BspListener listener;
  if (generate || refresh) {
    // dungeon generation
    if (!bsp) {
      // create the bsp
      bsp = new TCODBsp(0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT);
    } else {
      // restore the nodes size
      bsp->resize(0, 0, SAMPLE_SCREEN_WIDTH, SAMPLE_SCREEN_HEIGHT);
    }
    memset(map, '#', sizeof(char) * SAMPLE_SCREEN_WIDTH * SAMPLE_SCREEN_HEIGHT);
    if (generate) {
      // build a new random bsp tree
      bsp->removeSons();
      bsp->splitRecursive(
          NULL, bspDepth, minRoomSize + (roomWalls ? 1 : 0), minRoomSize + (roomWalls ? 1 : 0), 1.5f, 1.5f);
    }
    // create the dungeon from the bsp
    bsp->traverseInvertedLevelOrder(&listener, &map);
    generate = false;
    refresh = false;
  }
  sampleConsole.clear();
  sampleConsole.setDefaultForeground(TCODColor::white);
  sampleConsole.printf(
      1,
      1,
      "ENTER : rebuild bsp\nSPACE : rebuild dungeon\n+-: bsp depth %d\n*/: room size %d\n1 : random room size %s",
      bspDepth,
      minRoomSize,
      randomRoom ? "ON" : "OFF");
  if (randomRoom) sampleConsole.printf(1, 6, "2 : room walls %s", roomWalls ? "ON" : "OFF");
  // render the level
  for (int y = 0; y < SAMPLE_SCREEN_HEIGHT; y++) {
    for (int x = 0; x < SAMPLE_SCREEN_WIDTH; x++) {
      bool wall = (map[x][y] == '#');
      sampleConsole.setCharBackground(x, y, wall ? darkWall : darkGround, TCOD_BKGND_SET);
    }
  }
  if (key->vk == TCODK_ENTER || key->vk == TCODK_KPENTER) {
    generate = true;
  } else if (key->c == ' ') {
    refresh = true;
  } else if (key->c == '+') {
    bspDepth++;
    generate = true;
  } else if (key->c == '-' && bspDepth > 1) {
    bspDepth--;
    generate = true;
  } else if (key->c == '*') {
    minRoomSize++;
    generate = true;
  } else if (key->c == '/' && minRoomSize > 2) {
    minRoomSize--;
    generate = true;
  } else if (key->c == '1' || key->vk == TCODK_1 || key->vk == TCODK_KP1) {
    randomRoom = !randomRoom;
    if (!randomRoom) roomWalls = true;
    refresh = true;
  } else if (key->c == '2' || key->vk == TCODK_2 || key->vk == TCODK_KP2) {
    roomWalls = !roomWalls;
    refresh = true;
  }
}

/* ***************************
 * name generator sample
 * ***************************/
void render_name(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  static int curSet = 0;
  static float delay = 0.0f;
  static std::vector<std::string> names;
  static std::vector<std::string> sets;
  if (sets.size() == 0) {
    TCODList<char*> files{TCODSystem::getDirectoryContent("data/namegen", "*.cfg")};
    // parse all the files
    for (char* it : files) {
      TCODNamegen::parse((std::string("data/namegen/") + it).c_str());
    }
    // get the sets list
    TCODList<char*> sets_list{TCODNamegen::getSets()};
    sets = {sets_list.begin(), sets_list.end()};
  }
  while (names.size() >= 15) {
    names.erase(names.begin());  // remove the first element.
  }

  sampleConsole.setDefaultBackground(TCODColor::lightBlue);
  sampleConsole.clear();
  sampleConsole.setDefaultForeground(TCODColor::white);
  sampleConsole.printf(1, 1, "%s\n\n+ : next generator\n- : prev generator", sets.at(curSet).c_str());
  for (int i = 0; i < names.size(); ++i) {
    const std::string& name = names.at(i);
    if (name.length() < SAMPLE_SCREEN_WIDTH)
      sampleConsole.printf(SAMPLE_SCREEN_WIDTH - 2, 2 + i, TCOD_BKGND_NONE, TCOD_RIGHT, "%s", name.c_str());
  }

  delay += TCODSystem::getLastFrameLength();
  if (delay >= 0.5f) {
    delay -= 0.5f;
    // add a new name to the list
    names.emplace_back(TCODNamegen::generate(sets.at(curSet).c_str(), true));
  }
  if (key->c == '=') {
    curSet++;
    if (curSet == sets.size()) curSet = 0;
    names.emplace_back("======");
  } else if (key->c == '-') {
    curSet--;
    if (curSet < 0) curSet = static_cast<int>(sets.size()) - 1;
    names.emplace_back("======");
  }
}

/* ***************************
 * SDL callback sample
 * ***************************/
#ifndef NO_SDL_SAMPLE
static bool sdl_callback_enabled = false;

class SampleRenderer : public ITCODSDLRenderer {
 public:
  SampleRenderer() : effectNum(0), delay(3.0f) { noise = new TCODNoise(3); }
  ~SampleRenderer() { delete noise; }
  void render(void* sdlSurface) {
    SDL_Surface* screen = (SDL_Surface*)sdlSurface;
    // now we have almighty access to the screen's precious pixels !!
    // get the font character size
    int char_w, char_h;
    TCODSystem::getCharSize(&char_w, &char_h);
    // compute the sample console position in pixels
    int sample_x = SAMPLE_SCREEN_X * char_w;
    int sample_y = SAMPLE_SCREEN_Y * char_h;
    delay -= TCODSystem::getLastFrameLength();
    if (delay < 0.0f) {
      delay = 3.0f;
      effectNum = (effectNum + 1) % 3;
      if (effectNum == 2)
        sdl_callback_enabled = false;  // no forced redraw for burn effect
      else
        sdl_callback_enabled = true;
    }
    switch (effectNum) {
      case 0:
        blur(screen, sample_x, sample_y, SAMPLE_SCREEN_WIDTH * char_w, SAMPLE_SCREEN_HEIGHT * char_h);
        break;
      case 1:
        explode(screen, sample_x, sample_y, SAMPLE_SCREEN_WIDTH * char_w, SAMPLE_SCREEN_HEIGHT * char_h);
        break;
      case 2:
        burn(screen, sample_x, sample_y, SAMPLE_SCREEN_WIDTH * char_w, SAMPLE_SCREEN_HEIGHT * char_h);
        break;
    }
  }

 protected:
  TCODNoise* noise;
  int effectNum;
  float delay;

  void burn(SDL_Surface* screen, int sample_x, int sample_y, int sample_w, int sample_h) {
    int r_idx = screen->format->Rshift / 8;
    int g_idx = screen->format->Gshift / 8;
    int b_idx = screen->format->Bshift / 8;
    for (int x = sample_x; x < sample_x + sample_w; x++) {
      uint8_t* p = (uint8_t*)screen->pixels + x * screen->format->BytesPerPixel + sample_y * screen->pitch;
      for (int y = sample_y; y < sample_y + sample_h; y++) {
        int ir = 0, ig = 0, ib = 0;
        uint8_t* p2 = p + screen->format->BytesPerPixel;  // get pixel at x+1,y
        ir += p2[r_idx];
        ig += p2[g_idx];
        ib += p2[b_idx];
        p2 -= 2 * screen->format->BytesPerPixel;  // get pixel at x-1,y
        ir += p2[r_idx];
        ig += p2[g_idx];
        ib += p2[b_idx];
        p2 += screen->format->BytesPerPixel + screen->pitch;  // get pixel at x,y+1
        ir += p2[r_idx];
        ig += p2[g_idx];
        ib += p2[b_idx];
        p2 -= 2 * screen->pitch;  // get pixel at x,y-1
        ir += p2[r_idx];
        ig += p2[g_idx];
        ib += p2[b_idx];
        ir /= 4;
        ig /= 4;
        ib /= 4;
        p[r_idx] = ir;
        p[g_idx] = ig;
        p[b_idx] = ib;
        p += screen->pitch;
      }
    }
  }

  void explode(SDL_Surface* screen, int sample_x, int sample_y, int sample_w, int sample_h) {
    int r_idx = screen->format->Rshift / 8;
    int g_idx = screen->format->Gshift / 8;
    int b_idx = screen->format->Bshift / 8;
    TCODRandom* rng = TCODRandom::getInstance();
    int dist = (int)(10 * (3.0f - delay));
    for (int x = sample_x; x < sample_x + sample_w; x++) {
      uint8_t* p = (uint8_t*)screen->pixels + x * screen->format->BytesPerPixel + sample_y * screen->pitch;
      for (int y = sample_y; y < sample_y + sample_h; y++) {
        int ir = 0, ig = 0, ib = 0;
        for (int i = 0; i < 3; i++) {
          int dx = rng->getInt(-dist, dist);
          int dy = rng->getInt(-dist, dist);
          uint8_t* p2;
          p2 = p + dx * screen->format->BytesPerPixel;
          p2 += dy * screen->pitch;
          ir += p2[r_idx];
          ig += p2[g_idx];
          ib += p2[b_idx];
        }
        ir /= 3;
        ig /= 3;
        ib /= 3;
        p[r_idx] = ir;
        p[g_idx] = ig;
        p[b_idx] = ib;
        p += screen->pitch;
      }
    }
  }

  void blur(SDL_Surface* screen, int sample_x, int sample_y, int sample_w, int sample_h) {
    // let's blur that sample console
    float f[3], n = 0.0f;
    int r_idx = screen->format->Rshift / 8;
    int g_idx = screen->format->Gshift / 8;
    int b_idx = screen->format->Bshift / 8;
    f[2] = TCODSystem::getElapsedSeconds();
    for (int x = sample_x; x < sample_x + sample_w; x++) {
      uint8_t* p = (uint8_t*)screen->pixels + x * screen->format->BytesPerPixel + sample_y * screen->pitch;
      f[0] = (float)(x) / sample_w;
      for (int y = sample_y; y < sample_y + sample_h; y++) {
        int ir = 0, ig = 0, ib = 0;
        if ((y - sample_y) % 8 == 0) {
          f[1] = (float)(y) / sample_h;
          n = noise->getFbm(f, 3.0f);
        }
        int dec = (int)(3 * (n + 1.0f));
        int count = 0;
        switch (dec) {
          case 4:
            count += 4;
            // get pixel at x,y
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p -= 2 * screen->format->BytesPerPixel;  // get pixel at x+2,y
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p -= 2 * screen->pitch;  // get pixel at x+2,y+2
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p += 2 * screen->format->BytesPerPixel;  // get pixel at x,y+2
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p += 2 * screen->pitch;
          case 3:
            count += 4;
            // get pixel at x,y
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p += 2 * screen->format->BytesPerPixel;  // get pixel at x+2,y
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p += 2 * screen->pitch;  // get pixel at x+2,y+2
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p -= 2 * screen->format->BytesPerPixel;  // get pixel at x,y+2
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p -= 2 * screen->pitch;
          case 2:
            count += 4;
            // get pixel at x,y
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p -= screen->format->BytesPerPixel;  // get pixel at x-1,y
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p -= screen->pitch;  // get pixel at x-1,y-1
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p += screen->format->BytesPerPixel;  // get pixel at x,y-1
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p += screen->pitch;
          case 1:
            count += 4;
            // get pixel at x,y
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p += screen->format->BytesPerPixel;  // get pixel at x+1,y
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p += screen->pitch;  // get pixel at x+1,y+1
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p -= screen->format->BytesPerPixel;  // get pixel at x,y+1
            ir += p[r_idx];
            ig += p[g_idx];
            ib += p[b_idx];
            p -= screen->pitch;
            ir /= count;
            ig /= count;
            ib /= count;
            p[r_idx] = ir;
            p[g_idx] = ig;
            p[b_idx] = ib;
            break;
          default:
            break;
        }
        p += screen->pitch;
      }
    }
  }
};

void render_sdl(bool first, TCOD_key_t* key, TCOD_mouse_t* mouse) {
  if (first) {
    // use noise sample as background. rendering is done in SampleRenderer
    sampleConsole.setDefaultBackground(TCODColor::lightBlue);
    sampleConsole.setDefaultForeground(TCODColor::white);
    sampleConsole.clear();
    sampleConsole.printRectEx(
        SAMPLE_SCREEN_WIDTH / 2,
        3,
        SAMPLE_SCREEN_WIDTH,
        0,
        TCOD_BKGND_NONE,
        TCOD_CENTER,
        "The SDL callback gives you access to the screen surface so that you can alter the pixels one by one using SDL "
        "API or any API on top of SDL. SDL is used here to blur the sample console.\n\nHit TAB to enable/disable the "
        "callback. While enabled, it will be active on other samples too.\n\nNote that the SDL callback only works "
        "with SDL renderer.");
  }
  if (key->vk == TCODK_TAB) {
    sdl_callback_enabled = !sdl_callback_enabled;
    if (sdl_callback_enabled) {
      TCODSystem::registerSDLRenderer(new SampleRenderer());
    } else {
      TCODSystem::registerSDLRenderer(NULL);
    }
  }
}

#endif
// ***************************
// the list of samples
// ***************************
const std::vector<sample_t> samples = {
    {"  True colors        ", render_colors},
    {"  Offscreen console  ", render_offscreen},
    {"  Line drawing       ", render_lines},
    {"  Noise              ", render_noise},
    {"  Field of view      ", render_fov},
    {"  Path finding       ", render_path},
    {"  Bsp toolkit        ", render_bsp},
    {"  Image toolkit      ", render_image},
    {"  Mouse support      ", render_mouse},
    {"  Name generator     ", render_name},
#ifndef NO_SDL_SAMPLE
    {"  SDL callback       ", render_sdl},
#endif
};

// ***************************
// the main function
// ***************************
int main(int argc, char* argv[]) {
  SDL_LogSetAllPriority(SDL_LOG_PRIORITY_WARN);
  int curSample = 0;  // index of the current sample
  bool first = true;  // first time we render a sample
  TCOD_key_t key = {TCODK_NONE, 0};
  TCOD_mouse_t mouse;
  const char* font = "data/fonts/dejavu10x10_gs_tc.png";
  int nbCharHoriz = 0, nbCharVertic = 0;
  int argn;
  int fullscreenWidth = 0;
  int fullscreenHeight = 0;
  TCOD_renderer_t renderer = TCOD_RENDERER_SDL2;
  bool fullscreen = false;
  int fontFlags = TCOD_FONT_TYPE_GREYSCALE | TCOD_FONT_LAYOUT_TCOD, fontNewFlags = 0;
  bool creditsEnd = false;
  int cur_renderer = 0;
  static const std::vector<std::string> renderer_name = {
      "F1 GLSL   ", "F2 OPENGL ", "F3 SDL    ", "F4 SDL2   ", "F5 OPENGL2"};

  // initialize the root console (open the game window)
  for (argn = 1; argn < argc; argn++) {
    if (strcmp(argv[argn], "-font") == 0 && argn + 1 < argc) {
      argn++;
      font = argv[argn];
      fontFlags = 0;
    } else if (strcmp(argv[argn], "-font-nb-char") == 0 && argn + 2 < argc) {
      argn++;
      nbCharHoriz = atoi(argv[argn]);
      argn++;
      nbCharVertic = atoi(argv[argn]);
      fontFlags = 0;
    } else if (strcmp(argv[argn], "-fullscreen-resolution") == 0 && argn + 2 < argc) {
      argn++;
      fullscreenWidth = atoi(argv[argn]);
      argn++;
      fullscreenHeight = atoi(argv[argn]);
    } else if (strcmp(argv[argn], "-renderer") == 0 && argn + 1 < argc) {
      argn++;
      renderer = (TCOD_renderer_t)atoi(argv[argn]);
    } else if (strcmp(argv[argn], "-fullscreen") == 0) {
      fullscreen = true;
    } else if (strcmp(argv[argn], "-font-in-row") == 0) {
      fontNewFlags |= TCOD_FONT_LAYOUT_ASCII_INROW;
      fontFlags = 0;
    } else if (strcmp(argv[argn], "-font-greyscale") == 0) {
      fontNewFlags |= TCOD_FONT_TYPE_GREYSCALE;
      fontFlags = 0;
    } else if (strcmp(argv[argn], "-font-tcod") == 0) {
      fontNewFlags |= TCOD_FONT_LAYOUT_TCOD;
      fontFlags = 0;
    } else if (strcmp(argv[argn], "-help") == 0 || strcmp(argv[argn], "-?") == 0) {
      printf("options :\n");
      printf("-font <filename> : use a custom font\n");
      printf("-font-nb-char <nb_char_horiz> <nb_char_vertic> : number of characters in the font\n");
      printf("-font-in-row : the font layout is in row instead of columns\n");
      printf("-font-tcod : the font uses TCOD layout instead of ASCII\n");
      printf("-font-greyscale : antialiased font using greyscale bitmap\n");
      printf("-fullscreen : start in fullscreen\n");
      printf("-fullscreen-resolution <screen_width> <screen_height> : force fullscreen resolution\n");
      printf("-renderer <num> : set renderer. 0 : GLSL 1 : OPENGL 2 : SDL\n");
      exit(0);
    } else {
      // ignore parameter
    }
  }

  if (fontFlags == 0) fontFlags = fontNewFlags;
  TCODConsole::setCustomFont(font, fontFlags, nbCharHoriz, nbCharVertic);
  if (fullscreenWidth > 0) {
    TCODSystem::forceFullscreenResolution(fullscreenWidth, fullscreenHeight);
  }

  TCODConsole::initRoot(80, 50, "libtcod C++ sample", fullscreen, renderer);
  atexit(TCOD_quit);
  do {
    if (!creditsEnd) {
      creditsEnd = TCODConsole::renderCredits(56, 43, false);
    }

    // print the list of samples
    for (int i = 0; i < samples.size(); i++) {
      if (i == curSample) {
        // set colors for currently selected sample
        TCODConsole::root->setDefaultForeground(TCODColor::white);
        TCODConsole::root->setDefaultBackground(TCODColor::lightBlue);
      } else {
        // set colors for other samples
        TCODConsole::root->setDefaultForeground(TCODColor::grey);
        TCODConsole::root->setDefaultBackground(TCODColor::black);
      }
      // print the sample name
      TCODConsole::root->print(
          2, 46 - (static_cast<int>(samples.size()) - i), samples[i].name, TCOD_LEFT, TCOD_BKGND_SET);
    }
    // print the help message
    TCODConsole::root->setDefaultForeground(TCODColor::grey);
    TCODConsole::root->printf(
        79,
        46,
        TCOD_BKGND_NONE,
        TCOD_RIGHT,
        "last frame : %3d ms (%3d fps)",
        (int)(TCODSystem::getLastFrameLength() * 1000),
        TCODSystem::getFps());
    TCODConsole::root->printf(
        79,
        47,
        TCOD_BKGND_NONE,
        TCOD_RIGHT,
        "elapsed : %8dms %4.2fs",
        TCODSystem::getElapsedMilli(),
        TCODSystem::getElapsedSeconds());
    TCODConsole::root->printf(2, 47, "%c%c : select a sample", TCOD_CHAR_ARROW_N, TCOD_CHAR_ARROW_S);
    TCODConsole::root->printf(
        2, 48, "ALT-ENTER : switch to %s", TCODConsole::isFullscreen() ? "windowed mode  " : "fullscreen mode");

    // render current sample
    samples[curSample].render(first, &key, &mouse);
    first = false;

    // blit the sample console on the root console
    TCODConsole::blit(
        &sampleConsole,
        0,
        0,
        SAMPLE_SCREEN_WIDTH,
        SAMPLE_SCREEN_HEIGHT,  // the source console & zone to blit
        TCODConsole::root,
        SAMPLE_SCREEN_X,
        SAMPLE_SCREEN_Y  // the destination console & position
    );
    // erase the renderer in debug mode (needed because the root console is not cleared each frame)
    TCODConsole::root->printf(1, 1, "        ");
    /* display renderer list and current renderer */
    cur_renderer = TCODSystem::getRenderer();
    TCODConsole::root->setDefaultForeground(TCODColor::grey);
    TCODConsole::root->setDefaultBackground(TCODColor::black);
    TCODConsole::root->printf(42, 46 - (TCOD_NB_RENDERERS + 1), TCOD_BKGND_SET, TCOD_LEFT, "Renderer :");
    for (int i = 0; i < TCOD_NB_RENDERERS; i++) {
      if (i == cur_renderer) {
        /* set colors for current renderer */
        TCODConsole::root->setDefaultForeground(TCODColor::white);
        TCODConsole::root->setDefaultBackground(TCODColor::lightBlue);
      } else {
        /* set colors for other renderer */
        TCODConsole::root->setDefaultForeground(TCODColor::grey);
        TCODConsole::root->setDefaultBackground(TCODColor::black);
      }
      TCODConsole::root->print(42, 46 - (TCOD_NB_RENDERERS - i), renderer_name[i], TCOD_LEFT, TCOD_BKGND_SET);
    }

    // update the game screen
    TCODConsole::flush();

    // did the user hit a key ?
    TCODSystem::checkForEvent((TCOD_event_t)(TCOD_EVENT_KEY_PRESS | TCOD_EVENT_MOUSE), &key, &mouse);
    if (key.vk == TCODK_DOWN) {
      // down arrow : next sample
      curSample = (curSample + 1) % samples.size();
      first = true;
    } else if (key.vk == TCODK_UP) {
      // up arrow : previous sample
      curSample--;
      if (curSample < 0) curSample = static_cast<int>(samples.size()) - 1;
      first = true;
    } else if (key.vk == TCODK_ENTER && (key.lalt | key.ralt)) {
      // ALT-ENTER : switch fullscreen
      TCODConsole::setFullscreen(!TCODConsole::isFullscreen());
#ifdef TCOD_LINUX
    } else if (key.c == 'p') {
#else
    } else if (key.vk == TCODK_PRINTSCREEN) {
#endif
      if (key.lalt) {
        // ALT-PrintScreen : save to .asc format
        TCODConsole::root->saveApf("samples.apf");
      } else {
        // save screenshot
        TCODSystem::saveScreenshot(NULL);
      }
    } else if (key.vk == TCODK_F1) {
      // switch renderers with F1,F2,F3
      TCODSystem::setRenderer(TCOD_RENDERER_GLSL);
    } else if (key.vk == TCODK_F2) {
      TCODSystem::setRenderer(TCOD_RENDERER_OPENGL);
    } else if (key.vk == TCODK_F3) {
      TCODSystem::setRenderer(TCOD_RENDERER_SDL);
    } else if (key.vk == TCODK_F4) {
      TCODSystem::setRenderer(TCOD_RENDERER_SDL2);
    } else if (key.vk == TCODK_F5) {
      TCODSystem::setRenderer(TCOD_RENDERER_OPENGL2);
    }
  } while (!TCODConsole::isWindowClosed());
  TCOD_quit();
  return 0;
}
