#!/usr/bin/perl -w

use strict;

my $ORIENTED = 1;
my $DEBUG = 0;

my $SKIPPERS = shift @ARGV;
my $BOATS = shift @ARGV; 
my $EmatchesPerFlight = $BOATS/2;
my $EmatchesPerSkipper = $SKIPPERS -1;


my %Ematches;
my %Fmatches;

for(my $i=0;$i<$SKIPPERS-1;$i++)
{
	for(my $j=$i+1;$j<$SKIPPERS;$j++)
	{
		$Ematches{"($i,$j)"} = 1;
		#print "($i,$j)\n";
	}
}

my @oRounds = ();
my $round = 0;

while(<>)
{
	
	chomp;
	my @matches = split /\s+/, $_;
	die if scalar @matches > $EmatchesPerFlight;
	warn "iffy : expected $EmatchesPerFlight, found ".scalar @matches if 
		($EmatchesPerFlight == 3 and scalar @matches < $EmatchesPerFlight -1) or 
		($EmatchesPerFlight == 4 and scalar @matches < $EmatchesPerFlight -1);
	
	my $mId = 0;
	my @oFlight;
	foreach my $match(@matches)
	{
		my ($a, $b) = $match =~ m/\(([0-9\-]+),([0-9\-]+)\)/;	
		next if "$a" eq '-1' and $b eq '-1';
		die "unexpected match ($a,$b)" unless exists $Ematches{orderAlpha($a, $b)};
		die "match already seen" if exists $Fmatches{orderAlpha($a, $b)};
		$Fmatches{orderAlpha($a, $b)} = 1;
		$mId++;
		push @oFlight, [$a,$b];
	}
	push @oRounds, [@oFlight];
	$round++;
}

my $success = 1;
$success &= criteria1();
$success &= criteria2_and_3() if ($ORIENTED);
$success &= criteria_4_soft();
$success &= criteria_4_hard();
$success &= criteria_5() if ($ORIENTED);
if ($SKIPPERS == $BOATS)
{
	$success &= criteria_6();
}
else
{
	warn "Criteria 6 skipped for $SKIPPERS $BOATS\n";
}
if ($EmatchesPerFlight >= 5)
{
	warn "Criteria 7 not implemented yet!\n";
}
else
{
	warn "Criteria 7 skipped for $EmatchesPerFlight matches per flight\n";
}
$success &= criteria_8() if ($ORIENTED);
$success &= criteria_9() if ($ORIENTED);
$success &= criteria_10_11_12()  if $SKIPPERS > $BOATS;
warn "Finished OK=$success\n";

sub criteria1
{
	my $OK = 1;
	my $eCount = scalar keys %Ematches;
	my $fCount = scalar keys %Fmatches;
	if ($eCount != $fCount)
	{
		warn "\tRule 1: ".()." matches not seen";
		$OK = 0;
	}
	warn "Criteria 1 all OK\n" if $OK;
	warn "Criteria 1: failed\n" if ! $OK;

	return $OK;	
}

sub criteria2_and_3
{
	my %portSkipper;
	my %stbdSkipper;
	for(my $i=0;$i<$SKIPPERS;$i++)
	{
		$portSkipper{$i} = 0;
		$stbdSkipper{$i} = 0;	
	}
	my $ok = 1;
	
 	foreach my $flight (@oRounds)
 	{
 		foreach my $match (@$flight)
 		{
 			$portSkipper{$match->[0]}++;
 			$stbdSkipper{$match->[1]}++;
 		}
 	}
	
	if ($EmatchesPerSkipper %2 == 0)
	{
		foreach my $sk (sort keys %portSkipper)
		{
			if ($portSkipper{$sk} != $stbdSkipper{$sk})
			{
				warn "\tRule 2 fail : skipper $sk, port=".$portSkipper{$sk}." stbd=".$stbdSkipper{$sk}." \n";
				$ok = 0;
			}			
		}
	}
	else
	{
		foreach my $sk (sort keys %portSkipper)
		{
			if ($portSkipper{$sk} > $stbdSkipper{$sk} and $sk < $SKIPPERS /2)
			{
				warn "\tRule 3 fail : skipper $sk, port=".$portSkipper{$sk}." stbd=".$stbdSkipper{$sk}." \n";
				$ok = 0;
			} elsif ($portSkipper{$sk} < $stbdSkipper{$sk} and $sk > $SKIPPERS /2) {
				warn "\tRule 3 fail : skipper $sk, port=".$portSkipper{$sk}." stbd=".$stbdSkipper{$sk}." \n";
				$ok = 0;
			}
			
		}
		#TODO - criteria 3.	
	}
	
	for(my $i=0;$i<$SKIPPERS;$i++)
	{
		warn "Skipper $i port=".$portSkipper{$i}." stbd=".$stbdSkipper{$i}."\n" if $DEBUG;
	}

	
	warn "Criteria 2&3 all OK\n" if $ok;
	warn "Criteria 2&3 failed\n" if ! $ok;	
	return $ok;
}

sub criteria_4_soft
{
	my $OK = 1;
	for(my $flId=0;$flId < -1 + scalar @oRounds;$flId++)
	{
		next if (scalar @{$oRounds[$flId]} < $EmatchesPerFlight);
		my $lastMatchThis = $oRounds[$flId]->[$EmatchesPerFlight-1];
		my $firstMatchNext = $oRounds[$flId+1]->[0];
		#die 
		my @FS = (0,1);
		foreach my $i (@FS)
		{
			foreach my $j (@FS)
			{
				if ($lastMatchThis->[$i] == $firstMatchNext->[$j])
				{
					warn "\tRule 4_soft fail -- skipper ".$lastMatchThis->[$i]." sails in round $flId last match and round  ".($flId+1)." first match\n" ;
					$OK = 0;
				}
			}
		}
	}
	warn "Criteria 4(soft) all OK\n" if $OK;
	warn "Criteria 4(soft) failed\n" if ! $OK;	
	return $OK;

}

sub criteria_4_hard
{
	my $OK = 1;
	for(my $flId=0;$flId < -1 + scalar @oRounds;$flId++)
	{
		my $lastMatchThis = $oRounds[$flId]->[scalar @{$oRounds[$flId]} -1 ];
		my $firstMatchNext = $oRounds[$flId+1]->[0];
		#die 
		my @FS = (0,1);
		foreach my $i (@FS)
		{
			foreach my $j (@FS)
			{
				if  ($lastMatchThis->[$i] == $firstMatchNext->[$j])
				{
					warn "\tRule 4_hard fail -- skipper ".$lastMatchThis->[$i]." sails in round $flId last match and round  ".($flId+1)." first match\n";
					$OK = 0;
				}
			}
		}
	}
	warn "Criteria 4(hard) all OK\n" if $OK;
	warn "Criteria 4(hard) failed\n" if ! $OK;
	return $OK;	
}

sub criteria_5
{
	my %skipper;
	my $OK = 1;
	for(my $i=0;$i<$SKIPPERS;$i++)
	{
		$skipper{$i} = '';	
	}
	foreach my $flight (@oRounds)
 	{
 		foreach my $match (@$flight)
 		{
 			$skipper{$match->[0]} .= '0';
 			$skipper{$match->[1]} .= '1'; 
 		}
 	}
 	for(my $i=0;$i<$SKIPPERS;$i++)
	{
		if ($skipper{$i} =~ /000/)
		{
			warn "\tRule 5 fail: skipper $i has >2 consecutive port assigments\n"; $OK = 0;
		}
		if ($skipper{$i} =~ /111/)
		{
			warn "\tRule 5 fail: skipper $i has >2 consecutive stbd assigments\n" ; $OK = 0;
		}
	}
	warn "Criteria 5: all OK\n" if $OK;
	warn "Criteria 5: failed\n" if ! $OK;
	return $OK;	
}


sub criteria_6
{
	my %skipper;
	for(my $i=0;$i<$SKIPPERS;$i++)
	{
		$skipper{$i} = [(0)x$EmatchesPerFlight];	
	}	
	foreach my $flight (@oRounds)
 	{
 		my $matchId = 0;
 		foreach my $match (@$flight)
 		{
 			$skipper{$match->[0]}->[$matchId]++;
 			$skipper{$match->[1]}->[$matchId]++;
 			$matchId++;
 		}
 	}
 	for(my $i=0;$i<$SKIPPERS;$i++)
	{
		my @starts = @{ $skipper{$i} };
		warn "Skipper $i: @starts\n" if $DEBUG;
		#TODO we need a hard way of quantifying
		
		#Each skipper should be assigned to match 1, match 2, match 3 etc
		#as equally as possible.
	}
	return 1;
}

sub criteria_8
{
	my $OK = 1;
	foreach my $flight (@oRounds)
 	{
 		foreach my $match (@$flight)
 		{
 			#$match->[0] == $match->[1] -1 --> not OK
 			#$match->[0] +1 == $match->[1] --> OK
 			if ($match->[0] == $match->[1] -1 )
 			{
 				warn "\tRule 8 fail: (".$match->[0].",".$match->[1].") are misordered \n";
 				$OK = 0;
 			}
 		}
 	}
 	warn "Criteria 8: all OK\n" if $OK;
 	warn "Criteria 8: failed\n" if $OK;
	return $OK;
}

#simply checks if (1,0) is in the last flight
sub criteria_9
{
	my $lastFlight = $oRounds[$#oRounds];
	my $contains10 = 0;
	foreach my $match (@$lastFlight)
 	{	
 		if ($match->[0] == 1 and $match->[1] == 0)
 		{
 			$contains10 = 1;	
 		}
 	}
 	warn "Criteria 9 fail: Last flight does not contain match (1,0)" unless $contains10;
 	return $contains10;
}

sub criteria_10_11_12
{
	my $OK = 1;
	my %skippersPrevious;
	my %skippersThis;
	
	my $matchesPrevious = scalar @{$oRounds[0]};
	my $matchesThis;
	
	my %skipperChanges;
	
	my %flightChanges;
	
	my $matchId = 0;
	foreach my $match (@{$oRounds[0]})
	{
		$skippersPrevious{$_} = $matchId for @{$match};
		$matchId++;
	}
	
	my $changes = 0;
	
	for (my $flId = 1;$flId <scalar @oRounds;$flId++)
	{
		#warn "$flId";	
		$flightChanges{$flId} = 0;
		my $flight = $oRounds[$flId];
		my $matchId = 0;
		$matchesThis = scalar @$flight;
		foreach my $match (@$flight)
 		{
			for my $skipper (@{$match})
			{
				$skippersThis{$skipper} = $matchId;
				if (! exists $skippersPrevious{$skipper})
				{
					$skipperChanges{$skipper}++;
					#warn "Flight $flId: Skipper $skipper changes in\n";
					$changes++;
					$flightChanges{$flId}++;
				}
			}
			$matchId++;
 		}
 		
 		#now run the per-flight checks
 		foreach my $skipper (keys %skippersPrevious)
 		{
 			if ($skippersPrevious{$skipper} == $matchesPrevious)
 			{
 				if (! exists $skippersThis{$skipper})
 				{
 					$OK = 0;
 					warn "\tRule 11 fail: Flight ".($flId-1).", Skipper $skipper does not sail\n"; 	
 				}
 			}
 			
 		}
 		foreach my $skipper (sort keys %skippersThis)
 		{
 			if ($skippersThis{$skipper} == 0 and ! exists $skippersPrevious{$skipper}) {
				$OK = 0;
				warn "\tRule 12 fail: Flight ".($flId).", Changing-in skipper $skipper sails in match 0 \n"; 
			}
 		}
 		
 		%skippersPrevious = %skippersThis;
 		%skippersThis = ();
 		$matchesPrevious = $matchesThis;
	}
	my $minChanges = $SKIPPERS+2;
	my $maxChanges = 0;
	my $FminChanges = 2+scalar @oRounds;
	my $FmaxChanges = 0;
	for my $sk (sort keys %skipperChanges)
	{
		$minChanges = $skipperChanges{$sk} if  $skipperChanges{$sk} < $minChanges;
		$maxChanges = $skipperChanges{$sk} if  $skipperChanges{$sk} > $maxChanges;
	}
	warn "Changes by Skipper: minChanges=$minChanges and maxChanges=$maxChanges\n" if $DEBUG;
	warn "Changes are unbalanced: minChanges=$minChanges and maxChanges=$maxChanges\n" if ($minChanges < $maxChanges -1);

	my $sum = 0;
	for my $sk (sort keys %flightChanges)
	{
		$FminChanges = $flightChanges{$sk} if  $flightChanges{$sk} < $FminChanges;
		$FmaxChanges = $flightChanges{$sk} if  $flightChanges{$sk} > $FmaxChanges;
		$sum += $flightChanges{$sk};
		warn "Flight $sk changes " .$flightChanges{$sk} . "\n" if $DEBUG;
	}
	warn "Changes by Flight: minChanges=$FminChanges avgChanges=".($sum/scalar keys %flightChanges)." maxChanges=$FmaxChanges\n";
	
	warn "Criteria 10: Total $changes changes\n";
	warn "Criteria 10: all OK\n" if $OK;
	warn "Criteria 10: failed\n" if ! $OK;
	return $OK;
}

sub orderAlpha
{
	my ($a, $b) = @_;
	return "($a,$b)" if ($a < $b);
	return "($b,$a)"; 
}