## Conway's Game of Life
##	Rule 1 - Any live cell with fewer than two live neighbours dies (underpopulation)
##	Rule 2 - Any live cell with two or three live neighbours lives
##	Rule 3 - Any live cell with more than three live neighbours dies (overpopulation)
##	Rule 4 - Any dead cell with epos.xactly three live neighbours becomes a live cell (reproduction)
extends Node2D


enum CellStates {
	DEAD,
	ALIVE,
}

@onready var generation_counter: Label = $CanvasLayer/Debug/VBoxContainer/GenerationCounter
@onready var generation_timer: Timer = $GenerationTimer
@onready var living_cells_counter: Label = $CanvasLayer/Debug/VBoxContainer/LivingCellsCounter
@onready var world_seed_label: Label = $CanvasLayer/Debug/VBoxContainer/WorldSeed

@export var world_seed: int
@export var cell_size: Vector2 = Vector2(16, 16)
@export var cell_texture: Texture2D
@export var world_size: Vector2 = Vector2(32, 32)

var cell_instance
var generation: int = 1
var world: Array = []

var total_living: int = 0


func _ready() -> void:
	if not world_seed:
		world_seed = randi()
	seed(world_seed)
	world_seed_label.text = "World Seeed: %s" % world_seed

	generate_world()


## Check a cell against the Conway rules and return True if cell shoudl be alive
func cell_is_alive(pos: Vector2) -> bool:
	var neighbors := count_living_neighbors(pos)
	var currently_alive = world[pos.x][pos.y] is RID

	# Rule 1 - Any live cell with fewer than two live neighbours dies (underpopulation)
	if currently_alive and neighbors < 2:
		return false
	# Rule 2 - Any live cell with two or three live neighbours lives
	elif currently_alive and (neighbors == 2 or neighbors == 3):
		return true
	# Rule 3 - Any live cell with more than three live neighbours dies (overpopulation)
		return false
	# Rule 4 - Any dead cell with epos.xactly three live neighbours becomes a live cell (reproduction)
	elif not currently_alive and neighbors == 3:
		return true

	return false

## Count all neighbors surrounding a cell
func count_living_neighbors(pos: Vector2) -> int:
	var count := 0

	var x_min = 0 if pos.x - 1 < 0 else pos.x - 1
	var y_min = 0 if pos.y - 1 < 0 else pos.y - 1
	var x_max = world_size.x if pos.x + 2 > world_size.x else pos.x + 2
	var y_max = world_size.y if pos.y + 2 > world_size.y else pos.y + 2

	for x in range(x_min, x_max):
		for y in range(y_min, y_max):
			if x == pos.x and y == pos.y: continue # Current cell - Don't count
			if world[x][y] is RID:
				count += 1
	return count

## Loop through the world and create or kill cells depending on rules
func process_generation() -> void:
	generation += 1
	total_living = 0

	var new_world: Array= []
	for x in range(world_size.x):
		new_world.append([])
		new_world[x].resize(world_size.x)
		for y in range(world_size.y):
			var pos = Vector2(x, y)
			if cell_is_alive(pos):
				total_living += 1
				new_world[x][y] = create_cell(pos)
			else:
				new_world[x][y] = kill_cell(pos)

	world = new_world


## Create the cell using the rendering server
func create_cell(pos: Vector2) -> RID:
	var world_size = world.size()
	if world[pos.x][pos.y] is RID: return world[pos.x][pos.y]

	var rs = RenderingServer

	cell_instance = rs.canvas_item_create()
	rs.canvas_item_set_parent(cell_instance, get_canvas_item())

	var rect: Rect2 = Rect2(-cell_size.x/2, -cell_size.y/2, cell_size.x, cell_size.y) # Centered with cell size
	rs.canvas_item_add_texture_rect(cell_instance, rect, cell_texture)

	var pos_fixed = Vector2(pos.x * cell_size.x, pos.y * cell_size.y)
	var trans = Transform2D(0, pos_fixed)
	rs.canvas_item_set_transform(cell_instance, trans)

	return cell_instance

func kill_cell(pos: Vector2) -> CellStates:
	if world[pos.x][pos.y] is RID:
		RenderingServer.free_rid(world[pos.x][pos.y])
	return CellStates.DEAD


## Generate the world with the initial cells
func generate_world() -> void:
	for x in range(world_size.x):
		world.append([])
		world[x].resize(world_size.x)
		for y in range(world_size.y):
			if randi_range(0, 1):
				total_living += 1
				world[x][y] = create_cell(Vector2(x, y))
			else:
				world[x][y] = CellStates.DEAD


func _on_generation_timer_timeout() -> void:
	process_generation()

	generation_counter.text = "Generation: %s" % generation
	living_cells_counter.text = "Living Cells: %s" % total_living

	generation_timer.start()