#!/usr/bin/perl

# In hi-res graphics mode (6), reserving 5 lines for status display, then hope
# to skip 0-15 lines during the next two.

# For the purposes of these calculations, lines start at the point the first
# piece of video data is latched.  So linestart + 1 cycle sees the first
# transition of DA0.

# Obviously for skipping zero lines, we leave everything alone!  But for
# everything else, can start messing after the active area of line 4.  The
# first time we can change anything that causes an address skip is cycle
# 4*57+32 (but can prep things earlier if it would not affect the end of
# scanline 4).

# At that time (prior to HS fall), we have this status:

# Mode = 6
#  B3o=0 |  XD2=0  XD3=0 |  B4i=B3o=0  B4o=0 |  YD2=1  YD3=1  YD4=0 |  B5i=YD3=1  B5o=1

# Need to test this, but for now I'm going to operate on the assumption that I
# can't affect anything while HS is low.  That might be nonsense in which case
# maybe I can optimise later.  For safety, assumimg HS duration is _5_ cycles
# instead of 4.

# I'm moderately certain that most of this _will_ work during HS, and therefore
# I could probably pull the whole thing off in a single scanline.  How cool
# would that be?

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

my %schedules = ();

$schedules{1} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'sta', 'v2s' ],
};

$schedules{2} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'sta', 'v2s' ],
};

$schedules{3} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'sta', 'v2s' ],
};

$schedules{4} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'sta', 'v2s' ],
};

$schedules{5} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'sta', 'v2s' ],
};

$schedules{6} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'sta', 'v2s' ],
};

$schedules{7} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'sta', 'v2s' ],
};

$schedules{8} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'std', 'v1c' ],
		5*57+23 => [ 'sta', 'v2s' ],
};

$schedules{9} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'std', 'v1c' ],
		5*57+23 => [ 'std', 'v1c' ],
		5*57+28 => [ 'sta', 'v2s' ],
};

$schedules{10} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'std', 'v1c' ],
		5*57+23 => [ 'std', 'v1c' ],
		5*57+28 => [ 'std', 'v1c' ],
		5*57+33 => [ 'sta', 'v2s' ],
};

$schedules{11} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'std', 'v1c' ],
		5*57+23 => [ 'std', 'v1c' ],
		5*57+28 => [ 'std', 'v1c' ],
		5*57+33 => [ 'std', 'v1c' ],
		5*57+38 => [ 'sta', 'v2s' ],
};

$schedules{12} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'std', 'v1c' ],
		5*57+23 => [ 'std', 'v1c' ],
		5*57+28 => [ 'std', 'v1c' ],
		5*57+33 => [ 'std', 'v1c' ],
		5*57+38 => [ 'std', 'v1c' ],
		5*57+43 => [ 'sta', 'v2s' ],
};

$schedules{13} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'std', 'v1c' ],
		5*57+23 => [ 'std', 'v1c' ],
		5*57+28 => [ 'std', 'v1c' ],
		5*57+33 => [ 'std', 'v1c' ],
		5*57+38 => [ 'std', 'v1c' ],
		5*57+43 => [ 'std', 'v1c' ],
		6*57+3 => [ 'sta', 'v2s' ],
};

$schedules{14} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'std', 'v1c' ],
		5*57+23 => [ 'std', 'v1c' ],
		5*57+28 => [ 'std', 'v1c' ],
		5*57+33 => [ 'std', 'v1c' ],
		5*57+38 => [ 'std', 'v1c' ],
		5*57+43 => [ 'std', 'v1c' ],
		6*57+3 => [ 'std', 'v1c' ],
		6*57+8 => [ 'sta', 'v2s' ],
};

$schedules{15} = {
		4*57+29 => [ 'sta', 'v2c' ],
		4*57+33 => [ 'std', 'v1c' ],
		4*57+38 => [ 'std', 'v1c' ],
		4*57+50 => [ 'std', 'v1c' ],
		4*57+55 => [ 'std', 'v1c' ],
		5*57+3 => [ 'std', 'v1c' ],
		5*57+8 => [ 'std', 'v1c' ],
		5*57+13 => [ 'std', 'v1c' ],
		5*57+18 => [ 'std', 'v1c' ],
		5*57+23 => [ 'std', 'v1c' ],
		5*57+28 => [ 'std', 'v1c' ],
		5*57+33 => [ 'std', 'v1c' ],
		5*57+38 => [ 'std', 'v1c' ],
		5*57+43 => [ 'std', 'v1c' ],
		6*57+3 => [ 'std', 'v1c' ],
		6*57+8 => [ 'std', 'v1c' ],
		6*57+13 => [ 'sta', 'v2s' ],
};

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

my $schedule_name = shift @ARGV;
my $schedule = $schedules{$schedule_name} // {};

my @mode_ydiv = ( 12, 1, 3, 1, 2, 1, 1, 1 );
my @mode_xdiv = ( 1, 3, 1, 2, 1, 1, 1, 1 );
my @mode_clr = ( 4, 3, 4, 3, 4, 3, 4, 0 );
my %regmod = (
		v0c => 0, v0s => 1,
		v1c => 2, v1s => 3,
		v2c => 4, v2s => 5,
	     );

my %modestr = (
		0 => "YD12,XD1 (000)",
		1 => "YD1,XD3  (001)",
		2 => "YD3,XD1  (010)",
		3 => "YD1,XD2  (011)",
		4 => "YD2,XD1  (100)",
		5 => "YD1,XD1  (101)",
		6 => "YD1,XD1  (110)",
		7 => "YD1,XD1  (111)",
	      );

my $ground = init_counter("GND");
my $b3_0 = init_counter("B3o");
my $xdiv2 = init_counter("XD2", $b3_0);
my $xdiv3 = init_counter("XD3", $b3_0);
my $b4 = init_counter("B4o", $b3_0, "B4i");
my $ydiv2 = init_counter("YD2", $b4);
my $ydiv3 = init_counter("YD3", $b4);
my $ydiv4 = init_counter("YD4", $ydiv3);
my $b15_5 = init_counter("B5o", $ydiv4, "B5i");

my $mode = 0;
set_mode(6);

my %last_print = ( 'B3o' => 0 );

for my $line (0..6) {

	print_state(" ", ">>>>> LINE $line >>>>>>");

	cpu_cycle();

	for (0..15) {
		set_b3_0($b3_0->{value}+1);
		cpu_cycle();
	}

	for (0..15) {
		set_b3_0($b3_0->{value}+1);
		cpu_cycle();
	}

	for (0..9) {
		set_b3_0($b3_0->{value}+1);
		cpu_cycle();
	}

	for (0..4) {
		cpu_cycle();
	}
	
	hsync(1);
	print_state(" ", "HS fall");

	for (0..4) {
		cpu_cycle();
	}

	hsync(0);
	print_state(" ", "HS rise");

	for (0..3) {
		cpu_cycle();
	}
}

print_state(" ", "-------------------");

exit 0;

sub cpu_cycle {
	my $force = shift;
	if (!exists $last_print{'B3o'} || $b3_0->{output} != $last_print{'B3o'}) {
		print_state(" ", "B3o->".($b3_0->{output} ? "1" : "0"));
		$last_print{'B3o'} = $b3_0->{output};
	} elsif ($force) {
		print_state(" ", "");
	}
	if (exists $schedule->{$cyc}) {
		my $ent = $schedule->{$cyc};
		my $op = shift @$ent;
		if ($op eq 'sta') {
			my $reg = shift @$ent;
			print_state("/", "sta <reg_sam_$reg");
			assert_empty($cyc+1);
			assert_empty($cyc+2);
			assert_empty($cyc+3);
			my $m = new_mode($mode, $regmod{$reg});
			$schedule->{$cyc+3} = [ 'mode', $m ];
		} elsif ($op eq 'std') {
			my $reg = shift @$ent;
			print_state("/", "std <reg_sam_$reg");
			assert_empty($cyc+1);
			assert_empty($cyc+2);
			assert_empty($cyc+3);
			assert_empty($cyc+4);
			my $m = new_mode($mode, $regmod{$reg});
			$schedule->{$cyc+3} = [ 'mode', $m ];
			$m = new_mode($m, $regmod{$reg}+1);
			$schedule->{$cyc+4} = [ 'mode', $m ];
		} elsif ($op eq 'mode') {
			my $newmode = shift @$ent;
			set_mode($newmode);
			my $str = $modestr{$newmode};
			print_state("/", $str);
			$mode = $newmode;
		}
	} else {
		#print_state("/", "-");
	}
	$cyc++;
}

sub assert_empty {
	my $cyc = shift;
	if (exists $schedule->{$cyc}) {
		print "OVERLAP AT CYCLE $cyc: [ ".join(", ", @{$schedule->{$cyc}})." ]\n";
	} else {
		$schedule->{$cyc} = [ "-" ];
	}
}

sub print_output {
	my $clock = shift;
	my $name = $clock->{output_name};
	my $value = shift // $clock->{output};
	my $changed = (exists $last_print{$name} && $last_print{$name} != $value) ? "*" : " ";
	printf " %s%s=%d", $changed, $name, $value;
	$last_print{$name} = $value;
}

sub print_input {
	my $clock = shift;
	my $name = $clock->{input_name};
	my $iclock = $clock->{input_from};
	my $value = $iclock->{output_name}."=".($clock->{input_from}->{output} ? "1" : "0");
	my $changed = (exists $last_print{$name} && $last_print{$name} ne $value) ? "*" : " ";
	printf " %s%s=%s", $changed, $name, $value;
	$last_print{$name} = $value;
}

sub print_state {
	my $adorn = shift;
	my $text = shift;
	printf "%3d%s %-20s", $cyc, $adorn, $text;

	print "|";
	print_output($b3_0);

	print " |";
	print_output($xdiv2);
	print_output($xdiv3);

	print " |";
	print_input($b4);
	print_output($b4);

	print " |";
	print_output($ydiv2);
	print_output($ydiv3);
	print_output($ydiv4);

	print " |";
	print_input($b15_5);
	print_output($b15_5, $b15_5->{value} >> 5);
	print "\n";
}

sub new_mode {
	my $mode = shift;
	my $regmod = shift;
	if ($regmod == 0) {
		$mode &= 0x6;
	} elsif ($regmod == 1) {
		$mode |= 0x1;
	} elsif ($regmod == 2) {
		$mode &= 0x5;
	} elsif ($regmod == 3) {
		$mode |= 0x2;
	} elsif ($regmod == 4) {
		$mode &= 0x3;
	} elsif ($regmod == 5) {
		$mode |= 0x4;
	}
	return $mode;
}

sub vaddr {
	return $b15_5->{value} | $b4->{value} | $b3_0->{value};
}

sub hsync {
	my $active = shift;
	return if (!$active);
	if ($mode_clr[$mode] == 4) {
		$b3_0->{value} = 0;
		$b3_0->{output} = 0;
		$xdiv2->{input} = 0;
		$xdiv3->{input} = 0;
		$b4->{input} = 0;
		$b4->{value} = 0;
		$b4->{output} = 0;
		update_ydiv2_input();
		update_ydiv3_input();
		update_ydiv4_input();
		update_b15_5_input();
	} elsif ($mode_clr[$mode] == 3) {
		$b3_0->{value} = 0;
		$b3_0->{output} = 0;
		update_xdiv2_input();
		update_xdiv3_input();
		update_b4_input();
	}
}

sub init_counter {
	my $output_name = shift;
	my $input = shift;
	my $input_name = shift;
	my %counter = (
		input => 0,
		value => 0,
		output => 0,
		output_name => $output_name,
		input_name => $input_name,
		input_from => $input,
	);
	if (defined $input) {
		$counter{input} = $input->{output};
	}
	return \%counter;
}

sub set_b3_0 {
	my $v = shift;
	$b3_0->{value} = $v & 15;
	$b3_0->{output} = ($v & 8) != 0;
	update_xdiv2_input();
	update_xdiv3_input();
	update_b4_input();
}

sub update_xdiv2_input {
	my $input = $xdiv2->{input_from}->{output};
	if ($input != $xdiv2->{input}) {
		$xdiv2->{input} = $input;
		if (!$input) {
			$xdiv2->{value} ^= 1;
			$xdiv2->{output} = $xdiv2->{value} != 0;
			update_b4_input();
		}
	}
}

sub update_xdiv3_input {
	my $input = $xdiv3->{input_from}->{output};
	if ($input != $xdiv3->{input}) {
		$xdiv3->{input} = $input;
		if (!$input) {
			$xdiv3->{value} = ($xdiv3->{value} + 1) % 3;
			$xdiv3->{output} = ($xdiv3->{value} & 2) != 0;
			update_b4_input();
		}
	}
}

sub update_b4_input {
	my $input = $b4->{input_from}->{output};
	if ($input != $b4->{input}) {
		$b4->{input} = $input;
		if (!$input) {
			$b4->{value} ^= 0x10;
			$b4->{output} = $b4->{value} != 0;
			update_ydiv2_input();
			update_ydiv3_input();
			update_ydiv4_input();
			update_b15_5_input();
		}
	}
}

sub update_ydiv2_input {
	my $input = $ydiv2->{input_from}->{output};
	if ($input != $ydiv2->{input}) {
		$ydiv2->{input} = $input;
		if (!$input) {
			$ydiv2->{value} ^= 1;
			$ydiv2->{output} = $ydiv2->{value} != 0;
			update_b15_5_input();
		}
	}
}

sub update_ydiv3_input {
	my $input = $ydiv3->{input_from}->{output};
	if ($input != $ydiv3->{input}) {
		$ydiv3->{input} = $input;
		if (!$input) {
			$ydiv3->{value} = ($ydiv3->{value} + 1) % 3;
			$ydiv3->{output} = ($ydiv3->{value} & 2) != 0;
			update_ydiv4_input();
			update_b15_5_input();
		}
	}
}

sub update_ydiv4_input {
	my $input = $ydiv4->{input_from}->{output};
	if ($input != $ydiv4->{input}) {
		$ydiv4->{input} = $input;
		if (!$input) {
			$ydiv4->{value} = ($ydiv4->{value} + 1) % 4;
			$ydiv4->{output} = ($ydiv4->{value} & 2) != 0;
			update_b15_5_input();
		}
	}
}

sub update_b15_5_input {
	my $input = $b15_5->{input_from}->{output};
	if ($input != $b15_5->{input}) {
		$b15_5->{input} = $input;
		if (!$input) {
			$b15_5->{value} += 0x20;
		}
	}
}

sub set_mode {
	my $new_mode = shift;
	my $old_ydiv = $mode_ydiv[$mode];
	my $old_xdiv = $mode_xdiv[$mode];
	my $new_ydiv = $mode_ydiv[$new_mode];
	my $new_xdiv = $mode_xdiv[$new_mode];

	if ($new_ydiv != $old_ydiv) {
		if ($new_ydiv == 12) {
			if ($old_ydiv == 3) {
				$b15_5->{input_from} = $ground;
				update_b15_5_input();
			} elsif ($old_ydiv == 2) {
				$b15_5->{input_from} = $b4;
				update_b15_5_input();
			}
			$b15_5->{input_from} = $ydiv4;
		} elsif ($new_ydiv == 3) {
			if ($old_ydiv == 12) {
				$b15_5->{input_from} = $ground;
				update_b15_5_input();
			}
			$b15_5->{input_from} = $ydiv3;
		} elsif ($new_ydiv == 2) {
			if ($old_ydiv == 12) {
				$b15_5->{input_from} = $b4;
				update_b15_5_input();
			}
			$b15_5->{input_from} = $ydiv2;
		} else {
			$b15_5->{input_from} = $b4;
		}
		update_ydiv2_input();
		update_ydiv3_input();
		update_ydiv4_input();
		update_b15_5_input();
	}

	if ($new_xdiv != $old_xdiv) {
		if ($new_xdiv == 3) {
			if ($old_xdiv == 2) {
				$b4->{input_from} = $ground;
				update_b4_input();
			}
			$b4->{input_from} = $xdiv3;
		} elsif ($new_xdiv == 2) {
			if ($old_xdiv == 3) {
				$b4->{input_from} = $ground;
				update_b4_input();
			}
			$b4->{input_from} = $xdiv2;
		} else {
			$b4->{input_from} = $b3_0;
		}
		update_xdiv2_input();
		update_xdiv3_input();
		update_b4_input();
	}

	$mode = $new_mode;
}
