#!/usr/bin/perl
use v5.14;
use warnings;
use lib '.';

use asm;

sub update_leds {
	my ($x) = @_;
	route $x + 0, $x + 1, 4;
	route $x + 1, $x + 2, 3;
	route $x + 2, $x + 3, 2;
	route $x + 3, $x + 4, 1;
}

sub xori {
	my ($addrA, $imm, $addrC) = @_;
	storei 42, $imm;
	xor_ $addrA, 42, $addrC;
}

sub andi {
	my ($addrA, $imm, $addrC) = @_;
	storei 42, $imm;
	and_ $addrA, 42, $addrC;
}

# we want everyone to have
#
# own   quadrant at mem[0]
# east  quadrant at mem[11]
# south quadrant at mem[12]
# SE    quadrant at mem[13]

my $q_east = 11;
my $q_south = 12;
my $q_SE = 13;

chip_select 4, sub { storei 0, 0x247 }; # glider
chip_select $_, sub { ledi $_ } for 1, 2, 4, 8;


update_leds 0;

chip_select 1, sub { route 0,  11 };
chip_select 2, sub { route 11, 12 };
chip_select 4, sub { route 12, 13 };

chip_select 2, sub { route 0,  13 };
chip_select 4, sub { route 13, 12 };
chip_select 8, sub { route 12, 11 };

chip_select 4, sub { route 0,  11 };
chip_select 8, sub { route 11, 12 };
chip_select 1, sub { route 12, 13 };

chip_select 8, sub { route 0,  13 };
chip_select 1, sub { route 13, 12 };
chip_select 2, sub { route 12, 11 };


sub news_to_mem {
	my ($dir, $mem) = @_;
	alu3 alu_zero, alu_select_f, 0, 0, $mem, flag_news($dir), flag_zero
}

my $and_AB = alu_of_function { $a & $b };

sub compute_neighbour {
	my ($dest, $dir, $mask, $other) = @_;
	storei $dest, $mask;
	alu3 $and_AB, $and_AB, 0, $dest, $dest, flag_zero, flag_zero;
	news_to_mem $dir, $dest;

	storei $dest + 1, 0xFFFF - $mask;
	alu3 $and_AB, $and_AB, $other, $dest + 1, $dest + 1, flag_zero, flag_zero;
	alu3 alu_zero, alus_addAF, $dest, 0, $dest, flag_news($dir), flag_zero
}

# N neighbour
compute_neighbour 1, 0, 0x0FFF, $q_south;
# E neighbour
compute_neighbour 2, 1, 0xEEEE, $q_east;
# S neighbour
compute_neighbour 3, 2, 0xFFF0, $q_south;
# W neighbour
compute_neighbour 4, 3, 0x7777, $q_east;

sub compute_diagonal_neighbour {
	my ($dest, $dir, @masks) = @_;
	die 'expected exactly 4 @masks' unless @masks == 4;
	storei $dest, $masks[0];
	alu3 $and_AB, alu_select_a, 0, $dest, 0, flag_zero, flag_zero;
	news_to_mem $dir, $dest;

	for my $quad (1 .. 3) {
		storei $dest + 1, $masks[$quad];
		alu3 $and_AB, alu_select_a, 10 + $quad, $dest + 1, 10 + $quad, flag_zero, flag_zero;
		alu3 alu_zero, alus_addAF, $dest, 0, $dest, flag_news($dir), flag_zero;
	}
}

#update_leds $q_SE;

# NW neighbour
compute_diagonal_neighbour 5, 4, 0x0777, 0x0888, 0x7000, 0x8000;
#update_leds 5;
# NE neighbour
compute_diagonal_neighbour 6, 5, 0x0EEE, 0x0111, 0xE000, 0x1000;
#update_leds 6;
# SE neighbour
compute_diagonal_neighbour 7, 6, 0xEEE0, 0x1110, 0x000E, 0x0001;
#update_leds 7;
# SW neighbour (DODGY)
compute_diagonal_neighbour 8, 7, 0x7770, 0x8880, 0x0007, 0x0008;
#update_leds 8;

sub flag_to_memory {
	my ($address, $flag) = @_;
	alu3 alu_zero, alu_select_f, 0, 0, $address, $flag, flag_zero;
}

# now we add bits mem[1] .. mem[8]
# result should be mem[1] .. mem[3] (we don't distinguish 8 from 0)
my $F = 1;

# add 1 and 2, 3 and 4, 5 and 6, 7 and 8
for my $x (1, 3, 5, 7) {
	add $x, $x + 1, $x, $F;
	flag_to_memory $x + 1, $F;
}

# add 12 and 34, 56 and 78
for my $x (1, 5) {
	add $x, $x + 2, $x, $F;
	addC $x + 1, $x + 3, $x + 1, $F;
	flag_to_memory $x + 2, $F;
}

# add 12 and 56
add 1, 5, 1, $F;
addC 2, 6, 2, $F;
# F = F | 3 | 7
alu3 alu_or, alu_select_a, 3, 7, 3, $F, $F;

# should a new cell be born here? (do we have 3 neighbours?)
sub alu_birth { alu_of_function {; $a && $b && !$_ } }

# should a living cell survive here? (do we have 2 or 3 neighbours?)
sub alu_survival { alu_of_function {; $a && !$_  } }

# compute the state of this cell at the next tick
sub alu_step { alu_of_function {; $_ & ($a | $b) } }

# read from memory 1, and with memory 2 and not F, write into memory 1 (= birth-p)
alu3 alu_zero, alu_birth, 1, 2, 1, $F, flag_zero;
# read from memory 2, and with flag not F, write into flag F (= survive-p)
alu3 alu_survival, alu_select_a, 2, 0, 2, $F, $F;
# read from memory 0, memory 1, and flag F, write F and (mem0 or mem1) into memory 0
alu3 alu_zero, alu_step, 0, 1, 0, $F, $F; # also zeroes out flag F
