package MasRegion;
use strict;
use warnings;
use SimpleObject;
use CtUtilities qw /round_to offset_of_cell_neighbor fisher_yates_shuffle/;
use Math::CDF qw/qnorm qbeta/;
our @ISA = qw(SimpleObject);


sub create {
	my $class = shift;
	my $id = shift;
	return $class->SUPER::create(id => $id);
}

sub id {
	$_[0]->{id};
}

sub set_clipmap {
	my $self = shift;
	my $mapref = shift;
	$self->{clipmap} = $mapref;
}

sub clipmap {
	return $_[0]->{clipmap};
}
sub option {
      return $_[0]->{options}->{$_[1]};
}
sub set_option {
      $_[0]->{options}->{$_[1]} = $_[2];
}
sub prepare_agents {
	my $self = shift;
	my $poptable = shift;
	my $next_free_agent_id= shift;
	my $cellsize = shift;

	my $district = $self->get_attribute("id");
	#create agents and determine their farm size
	#loop over clusters in poptable		
	my $prevSt = 0;
	my @agents;
	
	for my $curSt (0..$#{$poptable}){
		my $numAgents = $poptable->[$curSt][1];
		my $numHa = $poptable->[$curSt][2];
		my $avgFarmSize = !$numAgents ? 0 : $numHa / $numAgents;
		my $maxFarmSize = $poptable->[$curSt][0] - $cellsize;
		my $minFarmSize = $prevSt;
		$minFarmSize += $cellsize if ($minFarmSize == 0);

		if ($maxFarmSize == 1000 - $cellsize) {
			$maxFarmSize = $avgFarmSize + ($avgFarmSize - $minFarmSize);
		}
		my $freely = $numHa - ($numAgents * $minFarmSize);
		
		for my $curAgent (0..$numAgents - 1) {
 			my $newagent = MasMapAgent->create(id => $next_free_agent_id++, 
								stratum => $poptable->[$curSt][0], 
								district => $district,
								);
			#top-up has to lie between 0 or the minimum required to still distribute all land
			# and the maximum allowed not to exceed either maxFarmSize or freely distributable land still available
			my $minTopup = 0;
			my $maxTopup = $maxFarmSize - $minFarmSize;
			$maxTopup = $freely if $freely < $maxTopup;
			my $AgentsToCome = $numAgents - 1 - $curAgent;
			$minTopup = $freely - ($maxTopup * $AgentsToCome) if ($freely - ($maxTopup * $AgentsToCome) > 0 );

			#draw random number to determine top-up in the range that has been allowed
			my $random;
			if ($maxFarmSize == 400 - $cellsize) {
				$random = qbeta(rand(1), 2, 7);
			}
			else {
				$random = rand(1);
			}
			my $Topup = $minTopup + $random * ($maxTopup - $minTopup);
			$Topup = round_to($Topup, $cellsize);
			my $newFarmSize = $minFarmSize + $Topup;
			$newagent->set_attribute("farm_size", $newFarmSize);
			push @agents, $newagent;
			$freely = $freely - $Topup;
			
		}
		$prevSt = $poptable->[$curSt][0];
	}
	$self->add_agents( @agents );
	return $next_free_agent_id;
}

sub save_agentinfotable_as_ascii {
	my $self = shift;
	my $fname = shift;
	open (my $fh, ">", $fname) or die "$!";
	print $fh "fstID\tDistrict\tStratum\tFst_X\tFst_Y\tFarm_Size\n";
	for my $i ($self->agents()) {
		my ($x, $y)= $i->get_farmstead();
		print $fh $i->id(). "\t".$i->get_attribute("district")."\t" .$i->get_attribute("stratum"). "\t$x\t$y\t". $i->get_attribute("farm_size")."\n";
	}
	close $fh;

}
sub potential_farmsteads {
	my $self = shift;
	my $id = shift;
	return defined ($id) ? $self->{fstsuittab}->[$id] : @{$self->{fstsuittab}};
}

sub set_potential_farmsteads {
	my $self = shift;
	my $arrayref = shift;
	$self->{fstsuittab} = $arrayref;
}

sub delete_potential_farmstead {
	my $self = shift;
	my $id = shift;
	my @fst = $self->potential_farmsteads();
	my @lower = @fst[0..($id -1)];
	my @upper = @fst[($id+1)..$#fst];
	push @lower, @upper;
	$self->set_potential_farmsteads(\@lower);
}
sub assign_farmstead {
	my $self = shift;
	my $agent = shift;
	my $id = shift;
	my $verbose = shift;
	my @fst = $self->potential_farmsteads();
	my $x = $fst[$id]->{x};
	my $y = $fst[$id]->{y};
	$agent->assign_farmstead($x,$y);
	print "Assigned farmstead $x,$y to agent ".$agent->id()."\n" if $verbose;
	$self->delete_potential_farmstead($id);
	$self->{fstmap}->set_cell($x,$y,$agent->id() );
	1;
}
sub create_blank_farmsteadmap {
	my $self = shift;
	my $fstpotmap = shift;
	my $fstmap = $fstpotmap->create_alike(-1);
	$self->{fstmap} = $fstmap;
	1;
}
sub farmsteadmap {
	$_[0]->{fstmap};
}

sub set_farmsteadmap {
	my $self = shift;
	my $mapref = shift;
	$self->{fstmap} = $mapref;
}

sub create_blank_propertymap {
	my $self = shift;
	my $proppotmap = shift;
	my $propmap = $proppotmap->create_copy();
	$propmap->reclassify({1 => -1, 0 => -9}, -9);
	$propmap->overlay("update", $self->farmsteadmap() );
	$propmap->create_polygonraster();
	$self->{propmap} = $propmap;
	1;
}
sub propertymap {
	$_[0]->{propmap};
}

sub set_propertymap {
	my $self = shift;
	my $mapref = shift;
	$self->{propmap} = $mapref;
}

sub distribute_farmsteads {
	my $self = shift;
	my $fstpotmap = shift;
	my $verbose = shift|| 0;
	$self->create_blank_farmsteadmap($fstpotmap);
	my $arrayofpot = $fstpotmap->report_cells_equal_to(1);
	
	$self->set_potential_farmsteads($arrayofpot  ); # make a table with all cells that may potentially harbour a farmstead
	for my $i ( $self->agents() ) {
		my $num_free_farmsteads = $self->potential_farmsteads();
	
		return 0 if ($num_free_farmsteads <= 0);
		
		my $id = int rand($num_free_farmsteads);
		
		$self->assign_farmstead($i, $id, $verbose);
	}


	return 1;
}

sub add_subregions {
	my $self = shift;
	$self->create_subregion_registry() if (not exists $self->{SubregionRegistry});
	$self->{SubregionRegistry}->add_objects(@_);
}
sub create_subregion_registry {
	my $self = shift;
	$self->{SubregionRegistry} = SimpleObjectRegistry->create();
}
sub subregions {
	$_[0]->{SubregionRegistry}->object_list();
}

sub get_subregion_by_id {
	$_[0]->{SubregionRegistry}->get_object_by_id($_[1]);
}

sub add_agents {
	my $self = shift;
	$self->create_agent_registry() if (not exists $self->{AgentRegistry});
	$self->{AgentRegistry}->add_objects(@_);
}
sub create_agent_registry {
	my $self = shift;
	$self->{AgentRegistry} = SimpleObjectRegistry->create();
}
sub agents {
	$_[0]->{AgentRegistry}->object_list();
}

sub get_agent_by_id {
	$_[0]->{AgentRegistry}->get_object_by_id($_[1]);
}

sub distribute_properties {
	my $self = shift;
	my $potentialplots = shift;
	my $cellsize = shift;
	my $avgplotsize = shift;
	my $maxlevel = shift;
	my $verbose = shift ||0;
	$maxlevel = 20 if (not defined $maxlevel);
	$self->create_blank_propertymap($potentialplots);
	my $finished = 0;
	my $avgplot = $avgplotsize / $cellsize;
	my $sigma = $avgplot *2;
	for my $curAgent ($self->agents()){
		my $tot = $curAgent->get_attribute("farm_size") / $cellsize - $cellsize; #set the total to be allocated for an agent to his farmsize minus 1 cell for the farmstead.
		$curAgent->set_attribute("notyetallocated", $tot);
		$curAgent->set_attribute("done", 0);
	}
	my $numAgents = $self->agents();
	my $numFinished = 0;
	
	while (not $finished) {

		
		AGENTLOOP: for my $curAgent ($self->agents()){
			my ($id_largest, $biggestavailable) = $self->{propmap}->get_id_and_size_of_largest_polygon_of_value(-1);
			print "Biggest Available: $biggestavailable\n" if $verbose;
			
			if ($curAgent->get_attribute("notyetallocated") == 0 and $curAgent->get_attribute("done") == 0) {
				$curAgent->set_attribute("done", 1);
				$numFinished++;
			}
			$finished = 1 if ($numFinished == $numAgents);
			next AGENTLOOP if ($curAgent->get_attribute("done") == 1);
			print "Trying to allocate plots for agent ".$curAgent->get_attribute("id")." :\n" if $verbose;
			print "Not yet allocated: ".$curAgent->get_attribute("notyetallocated")."\n" if $verbose;
			print "Numfinished: $numFinished\n" if $verbose;
			#randomly determine a chunk size
			my $chunk;	
			
			RANDLOOP: while (1) {
				if ($curAgent->get_attribute("notyetallocated") == 1){
					$chunk = 1;
					last RANDLOOP;
				}
				$chunk= int (qnorm(rand()) * $sigma + $avgplot);
				
				last RANDLOOP if ($chunk > 0 and $chunk <= $curAgent->get_attribute("notyetallocated") and $chunk <= $biggestavailable);
			}
			print "Chunk: $chunk\n" if $verbose;

			#find a free polygon which is at least as big as the chunk, starting from the agents farmstead
					
			my ($start_x, $start_y) = $curAgent->get_farmstead();
			my $level = 0;
			my $nothingfound = 1;
			my ($x,$y);
			POLYGONSEARCH: while ($nothingfound) {
				$level++;
				my $numout = 0;
				my @cellring = (0 .. 2**(2+$level) - 1);
				my @shuffledcells = fisher_yates_shuffle (\@cellring);
				
				PSINNER: for my $n (@shuffledcells) {
					my ($subx, $suby) = offset_of_cell_neighbor ($level, $n );
					$x = $start_x + $subx;
					$y = $start_y + $suby;
					

					if ($self->{propmap}->out_of_x_bounds($x) or $self->{propmap}->out_of_y_bounds($y)) {
						$numout++;
						next PSINNER;
					};
					next PSINNER unless ($self->{propmap}->get_cell($x,$y) == -1);
					next PSINNER unless ($self->{propmap}->get_polygon_size_by_cell($x,$y) >= $chunk);
					$nothingfound = 0;
					last PSINNER;
				}
				#if nothing has been found yet and all cells of the cell ring lie out of map bounds stop searching, and report that this chunk size is too large
				if ($nothingfound and $numout == @cellring) {
					print "No suitable polygon found in map! (out of bounds)\n" if $verbose;
					$biggestavailable = $chunk -1;
					next AGENTLOOP;
				}	
				# if nothing has been found within 20 cells randomly choose any polygon large enough and start with its handle cell (workaround because of memory problems)
				if ($nothingfound and $level > $maxlevel) {
					print "No suitable polygon found within 20 rings!\n" if $verbose;

					my $polyid = $self->{propmap}->get_id_of_random_polygon_of_value_and_larger_or_equal(-1, $chunk);
					unless (defined $polyid) {
						print "No suitable polygon found in map!\n" if $verbose;
					 	$biggestavailable = $chunk -1;
						next AGENTLOOP ;
					}
					($x,$y) = $self->{propmap}->get_handlecell_of_polygon_by_id($polyid);
					print "Workaround: Randomly selected suitable polygon $polyid \n" if $verbose;
					$nothingfound = 0;
				}
							
			}
			my $selectedpoly = $self->{propmap}->get_polygon_id_by_cell($x,$y);
			print "Found Polygon $selectedpoly\n" if $verbose;
			# distribute a part of the polygon of size chunk to the agent
			my $cellstodistribute = $chunk;
			my @stack;
			#push start cell on stack
			print "Distributing...\n" if $verbose;
			push @stack, {x => $x, y => $y, from => "start"};
			CELLDIST: while ($cellstodistribute and @stack) {
				
				my $c = pop @stack;
				my $curX = $c->{x};
				my $curY = $c->{y};
				my $from = $c->{from};
				
				#check if cell is part of the polygon
				if ($self->{propmap}->get_polygon_id_by_cell($curX,$curY) ==  $selectedpoly and $self->{propmap}->get_cell($curX, $curY) == -1) {
					#allocate the cell to the agent
					$self->{propmap}->set_cell($curX,$curY, $curAgent->id());
					$cellstodistribute -= 1;
					print "Allocated cell $curX, $curY to ".$curAgent->id()."\n" if $verbose;
					#push neighbouring cells on stack if not coming from there and not out of map bounds
					my @istack;
					push @istack, {x => $curX - 1, y=> $curY, from => "right"}   if (not $self->{propmap}->out_of_x_bounds($curX -1) and not $from eq "left");
					push @istack, {x => $curX + 1, y=> $curY, from => "left"}   if (not $self->{propmap}->out_of_x_bounds($curX +1) and not $from eq "right");
					push @istack, {x => $curX, y=> $curY - 1, from => "above"}   if (not $self->{propmap}->out_of_y_bounds($curY -1) and not $from eq "below");
					push @istack, {x => $curX, y=> $curY + 1, from => "below"}   if (not $self->{propmap}->out_of_y_bounds($curY +1) and not $from eq "above");
					my @shufstack =  fisher_yates_shuffle(\@istack) ;

					push @stack, @shufstack;

				}
				
			}
			undef @stack;
			my $allocated = $chunk - $cellstodistribute;
			print "Refreshing polygon raster after allocation\n"if $verbose;
			$self->{propmap}->refresh_polygonraster();
			$curAgent->add_to_attribute("notyetallocated", -1* $allocated);
		}
	}
}


1;



