require("util")

local function initRules(pattern)
  local b, s = pattern:match("B(.+)/S(.+)")
  B = {}
  for c in b:gmatch(".") do
    B[tonumber(c)] = true
  end
  S = {}
  for c in s:gmatch(".") do
    S[tonumber(c)] = true
  end
end

local function initColors()
  local colorbrewer = require("colorbrewer")
  local scheme = colorbrewer.random(2)
  ON = scheme[1]
  OFF = scheme[2]
  OFF_BG = { OFF[1], OFF[2], OFF[3], 0.9 }
end

local function initScreen()
  local width, height = love.graphics.getDimensions()
  WIDTH, HEIGHT = round2even(width), round2even(height)
  love.graphics.setBackgroundColor(OFF_BG)
end

local function initFont()
  FONT = love.graphics.newFont(16)
  love.graphics.setFont(FONT)
  FPS_WIDTH = FONT:getWidth("120 FPS")
  FPS_HEIGHT = FONT:getHeight()
end

local function initGrid()
  GRID = {}
  GRID.mt = {}
  setmetatable(GRID, GRID.mt)
  GRID.mt.__index = function(table, key)
    table[key] = {}
    return table[key]
  end
end

local function cloneGrid()
  local grid = {}
  for x, v in pairs(GRID) do
    grid[x] = {}
    for y, set in pairs(v) do
      grid[x][y] = set
    end
  end
  setmetatable(grid, grid.mt)
  return grid
end

local function drawGlider(x, y)
  GRID[x][y] = true
  x = x + UNITS
  y = y + UNITS
  GRID[x][y] = true
  x = x + UNITS
  GRID[x][y] = true
  y = y - UNITS
  GRID[x][y] = true
  y = y - UNITS
  GRID[x][y] = true
end

function love.load(args)
  local pattern = args[1] or "B3/S23"
  UNITS = args[2] or 8
  STEPS_PER_FRAME = args[3] or 1
  DRAGGING = false
  initRules(pattern)
  initColors()
  initScreen()
  initFont()
  initGrid()
  drawGlider(80, 80)
end

local function neighbors(x, y)
  local lowX = x - UNITS
  local highX = x + UNITS
  local lowY = y - UNITS
  local highY = y + UNITS
  local count = 0
  for i = lowX, highX do
    for j = lowY, highY do
      i = wrap(i, WIDTH)
      j = wrap(j, HEIGHT)
      if (i ~= x or j ~= y) and GRID[i][j] then
        count = count + 1
      end
    end
  end
  return count
end

function love.mousepressed(x, y, button)
  if button == 1 then
    DRAGGING = true
  elseif button == 2 then
    x = (x - (x % UNITS))
    y = (y - (y % UNITS))
    drawGlider(x, y)
  end
end

function love.mousereleased()
  DRAGGING = false
end

function love.mousemoved(x, y)
  if DRAGGING then
    x = (x - (x % UNITS))
    y = (y - (y % UNITS))
    GRID[x][y] = true
  end
end

FRAMELOCK_COUNTER = 0
FRAMELOCK_FPS = 30
function love.update(dt)
  pcall(function()
    require("lurker").update()
  end)
  FRAMELOCK_COUNTER = FRAMELOCK_COUNTER + dt
  if FRAMELOCK_COUNTER < (1 / FRAMELOCK_FPS) then
    return
  end
  FRAMELOCK_COUNTER = 0
  for _ = 1, STEPS_PER_FRAME do
    local grid = cloneGrid()
    for x = 0, WIDTH, UNITS do
      for y = 0, HEIGHT, UNITS do
        local current = GRID[x][y]
        local n = neighbors(x, y)
        if current then
          if not S[n] then
            grid[x][y] = false
          end
        else
          if B[n] then
            grid[x][y] = true
          end
        end
      end
    end
    GRID = grid
  end
end

local function paintFPS()
  local fps = tostring(love.timer.getFPS()) .. " FPS"
  love.graphics.setColor(ON)
  love.graphics.rectangle("fill", WIDTH - FPS_WIDTH - 10, HEIGHT - FPS_HEIGHT - 20, FPS_WIDTH + 30, FPS_HEIGHT + 10, 5, 5)
  love.graphics.setColor(OFF)
  love.graphics.print(fps, WIDTH - FPS_WIDTH - 5, HEIGHT - FPS_HEIGHT - 15)
end

function love.draw()
  for x, v in pairs(GRID) do
    if x ~= "mt" then
      for y, on in pairs(v) do
        if on then
          love.graphics.setColor(ON)
        else
          love.graphics.setColor(OFF_BG)
        end
        love.graphics.rectangle("fill", x, y, UNITS, UNITS)
      end
    end
  end
  paintFPS()
end
