// Published by devloop.org under MIT License, see: https://www.devloop.org/MIT-license.txt

#include <random>
#include <ctime>
#include <SDL/SDL.h>
#include <emscripten.h>
#include <emscripten/html5.h>

constexpr int screen_width_{ 768 };
constexpr int screen_height_{ 768 };

constexpr int world_width_{ 256 };
constexpr int world_height_{ 256 };

Uint32 curr_cells_[world_width_ * world_height_]{};
Uint32 next_cells_[world_width_ * world_height_]{};

SDL_Surface* life_surface_{ nullptr };

Uint32 dead_cell_{ 0 };

void Init(SDL_Surface* screen) {
  auto fmt{ screen->format };
  dead_cell_ = SDL_MapRGBA(fmt, 0, 0, 0, 255);
  life_surface_ = SDL_CreateRGBSurface(SDL_SWSURFACE, world_width_, world_height_, 
    fmt->BitsPerPixel, fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask);
  for (int i{ 0 }; i < world_width_ * world_height_; ++i)
    curr_cells_[i] = (rand() & 1) != 0 ? SDL_MapRGBA(fmt, 255, 0, 0, 255) : dead_cell_;
}

int IsAlive(int x, int y) {
  return x >= 0 && y >= 0 && x < world_width_ && y < world_height_ 
      && curr_cells_[y * world_width_ + x] != dead_cell_ ? 1 : 0;
}

int CountNeighbors(int x, int y) {
  return IsAlive(x - 1, y - 1) + IsAlive(x, y - 1) + IsAlive(x + 1, y - 1) 
        + IsAlive(x - 1, y) + IsAlive(x + 1, y) 
        + IsAlive(x - 1, y + 1) + IsAlive(x, y + 1) + IsAlive(x + 1, y + 1);
}

void UpdateCell(SDL_Surface* screen, int x, int y) {
  int num_neighbors{ CountNeighbors(x, y) };
  bool alive{ num_neighbors == 3 || (num_neighbors == 2 && IsAlive(x, y) == 1) };
  next_cells_[y * world_width_ + x] = 
    alive ? SDL_MapRGBA(screen->format, x, y, 255-x, 255) : dead_cell_;
}

void Step(void* arg) {
  SDL_Surface* screen{ static_cast<SDL_Surface*>(arg) };
  for (int y{ 0 }; y < world_height_; ++y) {
    for (int x{ 0 }; x < world_width_; ++x)
      UpdateCell(screen, x, y);
  }
  std::swap(curr_cells_, next_cells_);

  if (SDL_MUSTLOCK(life_surface_)) SDL_LockSurface(life_surface_);
  memcpy(life_surface_->pixels, curr_cells_, sizeof(Uint32) * world_width_ * world_height_);
  if (SDL_MUSTLOCK(life_surface_)) SDL_UnlockSurface(life_surface_);

  SDL_Rect dst{ 0, 0, screen_width_, screen_height_ };
  SDL_BlitScaled(life_surface_, nullptr, screen, &dst);
  SDL_Flip(screen);
}

void SpawnCell(SDL_Surface* screen, int x, int y) {
  x = (x * world_width_) / screen_width_ ;
  y = (y * world_height_) / screen_height_;
  curr_cells_[y * world_width_ + x] =  SDL_MapRGBA(screen->format, x, y, 255-x, 255);
}

EM_BOOL OnTouch(int event_type, const EmscriptenTouchEvent *touch_event, void *user_data) {
    auto* screen{ static_cast<SDL_Surface*>(user_data) };
    const auto* touch_point{ touch_event->touches };
    SpawnCell(screen, touch_point->targetX, touch_point->targetY);
    return true;
}

EM_BOOL OnMouse(int event_type, const EmscriptenMouseEvent *mouse_event, void *user_data) {
    auto* screen{ static_cast<SDL_Surface*>(user_data) };
    SpawnCell(screen, mouse_event->targetX, mouse_event->targetY);
    return true;
}

extern "C" int main(int argc, char** argv) {
  srand(time(nullptr));
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Surface* screen{ SDL_SetVideoMode(screen_width_, screen_height_, 32, SDL_SWSURFACE) };
  Init(screen);
  emscripten_set_mousedown_callback("canvas", screen, true, OnMouse);
  emscripten_set_mousemove_callback("canvas", screen, true, OnMouse);
  emscripten_set_touchstart_callback("canvas", screen, true, OnTouch);
  emscripten_set_touchmove_callback("canvas", screen, true, OnTouch);
  emscripten_set_main_loop_arg(Step, screen, 0, 1);
  SDL_Quit();
  return 0;
}
